Added context support to change description
Brian Vaughn committed
Jun 8, 2019 at 09:10 UTC
2d484013465a543b5ab83292e75f549f2a3085de
7 files changed
+787
-56
src/__tests__/__snapshots__/profilingCache-test.js.snap
+544
-31
@@ -94,21 +94,24 @@ exports[`ProfilingCache should collect data for each commit: CommitDetails commi
94
Object {
95
"changeDescriptions": Map {
96
3 => Object {
97
+ "context": null,
98
"didHooksChange": false,
99
"props": Array [],
99
- "state": Array [],
100
+ "state": null,
101
},
102
4 => Object {
103
+ "context": null,
104
"didHooksChange": false,
105
"props": Array [],
104
- "state": Array [],
106
+ "state": null,
107
},
108
2 => Object {
109
+ "context": null,
110
"didHooksChange": false,
111
"props": Array [
112
"count",
113
],
111
- "state": Array [],
114
+ "state": null,
115
},
116
},
117
"duration": 13,
@@ -137,16 +140,18 @@ exports[`ProfilingCache should collect data for each commit: CommitDetails commi
140
Object {
141
"changeDescriptions": Map {
142
3 => Object {
143
+ "context": null,
144
"didHooksChange": false,
145
"props": Array [],
142
- "state": Array [],
146
+ "state": null,
147
},
148
2 => Object {
149
+ "context": null,
150
"didHooksChange": false,
151
"props": Array [
152
"count",
153
],
149
- "state": Array [],
154
+ "state": null,
155
},
156
},
157
"duration": 10,
@@ -171,11 +176,12 @@ exports[`ProfilingCache should collect data for each commit: CommitDetails commi
176
Object {
177
"changeDescriptions": Map {
178
2 => Object {
179
+ "context": null,
180
"didHooksChange": false,
181
"props": Array [
182
"count",
183
],
178
- "state": Array [],
184
+ "state": null,
185
},
186
},
187
"duration": 10,
@@ -256,27 +262,30 @@ Object {
262
Array [
263
3,
264
Object {
265
+ "context": null,
266
"didHooksChange": false,
267
"props": Array [],
261
- "state": Array [],
268
+ "state": null,
269
},
270
],
271
Array [
272
4,
273
Object {
274
+ "context": null,
275
"didHooksChange": false,
276
"props": Array [],
269
- "state": Array [],
277
+ "state": null,
278
},
279
],
280
Array [
281
2,
282
Object {
283
+ "context": null,
284
"didHooksChange": false,
285
"props": Array [
286
"count",
287
],
279
- "state": Array [],
288
+ "state": null,
289
},
290
],
291
],
@@ -335,19 +344,21 @@ Object {
344
Array [
345
3,
346
Object {
347
+ "context": null,
348
"didHooksChange": false,
349
"props": Array [],
340
- "state": Array [],
350
+ "state": null,
351
},
352
],
353
Array [
354
2,
355
Object {
356
+ "context": null,
357
"didHooksChange": false,
358
"props": Array [
359
"count",
360
],
350
- "state": Array [],
361
+ "state": null,
362
},
363
],
364
],
@@ -390,11 +401,12 @@ Object {
401
Array [
402
2,
403
Object {
404
+ "context": null,
405
"didHooksChange": false,
406
"props": Array [
407
"count",
408
],
397
- "state": Array [],
409
+ "state": null,
410
},
411
],
412
],
@@ -662,19 +674,21 @@ Object {
674
Array [
675
3,
676
Object {
677
+ "context": null,
678
"didHooksChange": false,
679
"props": Array [],
667
- "state": Array [],
680
+ "state": null,
681
},
682
],
683
Array [
684
2,
685
Object {
686
+ "context": null,
687
"didHooksChange": false,
688
"props": Array [
689
"count",
690
],
677
- "state": Array [],
691
+ "state": null,
692
},
693
],
694
],
@@ -725,27 +739,30 @@ Object {
739
Array [
740
3,
741
Object {
742
+ "context": null,
743
"didHooksChange": false,
744
"props": Array [],
730
- "state": Array [],
745
+ "state": null,
746
},
747
],
748
Array [
749
5,
750
Object {
751
+ "context": null,
752
"didHooksChange": false,
753
"props": Array [],
738
- "state": Array [],
754
+ "state": null,
755
},
756
],
757
Array [
758
2,
759
Object {
760
+ "context": null,
761
"didHooksChange": false,
762
"props": Array [
763
"count",
764
],
748
- "state": Array [],
765
+ "state": null,
766
},
767
],
768
],
@@ -949,21 +966,24 @@ Object {
966
Object {
967
"changeDescriptions": Map {
968
3 => Object {
969
+ "context": null,
970
"didHooksChange": false,
971
"props": Array [],
954
- "state": Array [],
972
+ "state": null,
973
},
974
4 => Object {
975
+ "context": null,
976
"didHooksChange": false,
977
"props": Array [],
959
- "state": Array [],
978
+ "state": null,
979
},
980
2 => Object {
981
+ "context": null,
982
"didHooksChange": false,
983
"props": Array [
984
"count",
985
],
966
- "state": Array [],
986
+ "state": null,
987
},
988
},
989
"duration": 13,
@@ -989,16 +1009,18 @@ Object {
1009
Object {
1010
"changeDescriptions": Map {
1011
3 => Object {
1012
+ "context": null,
1013
"didHooksChange": false,
1014
"props": Array [],
994
- "state": Array [],
1015
+ "state": null,
1016
},
1017
2 => Object {
1018
+ "context": null,
1019
"didHooksChange": false,
1020
"props": Array [
1021
"count",
1022
],
1001
- "state": Array [],
1023
+ "state": null,
1024
},
1025
},
1026
"duration": 10,
@@ -1020,11 +1042,12 @@ Object {
1042
Object {
1043
"changeDescriptions": Map {
1044
2 => Object {
1045
+ "context": null,
1046
"didHooksChange": false,
1047
"props": Array [
1048
"count",
1049
],
1027
- "state": Array [],
1050
+ "state": null,
1051
},
1052
},
1053
"duration": 10,
@@ -1343,27 +1366,30 @@ Object {
1366
Array [
1367
3,
1368
Object {
1369
+ "context": null,
1370
"didHooksChange": false,
1371
"props": Array [],
1348
- "state": Array [],
1372
+ "state": null,
1373
},
1374
],
1375
Array [
1376
4,
1377
Object {
1378
+ "context": null,
1379
"didHooksChange": false,
1380
"props": Array [],
1356
- "state": Array [],
1381
+ "state": null,
1382
},
1383
],
1384
Array [
1385
2,
1386
Object {
1387
+ "context": null,
1388
"didHooksChange": false,
1389
"props": Array [
1390
"count",
1391
],
1366
- "state": Array [],
1392
+ "state": null,
1393
},
1394
],
1395
],
@@ -1422,19 +1448,21 @@ Object {
1448
Array [
1449
3,
1450
Object {
1451
+ "context": null,
1452
"didHooksChange": false,
1453
"props": Array [],
1427
- "state": Array [],
1454
+ "state": null,
1455
},
1456
],
1457
Array [
1458
2,
1459
Object {
1460
+ "context": null,
1461
"didHooksChange": false,
1462
"props": Array [
1463
"count",
1464
],
1437
- "state": Array [],
1465
+ "state": null,
1466
},
1467
],
1468
],
@@ -1477,11 +1505,12 @@ Object {
1505
Array [
1506
2,
1507
Object {
1508
+ "context": null,
1509
"didHooksChange": false,
1510
"props": Array [
1511
"count",
1512
],
1484
- "state": Array [],
1513
+ "state": null,
1514
},
1515
],
1516
],
@@ -1878,6 +1907,488 @@ Object {
1907
}
1908
`;
1909
1910
+exports[`ProfilingCache should record when props/state/hooks change: CommitDetails commitIndex: 0 1`] = `
1911
+Object {
1912
+ "changeDescriptions": Map {},
1913
+ "duration": 0,
1914
+ "fiberActualDurations": Map {
1915
+ 1 => 0,
1916
+ 2 => 0,
1917
+ 3 => 0,
1918
+ 4 => 0,
1919
+ 5 => 0,
1920
+ 6 => 0,
1921
+ 7 => 0,
1922
+ },
1923
+ "fiberSelfDurations": Map {
1924
+ 1 => 0,
1925
+ 2 => 0,
1926
+ 3 => 0,
1927
+ 4 => 0,
1928
+ 5 => 0,
1929
+ 6 => 0,
1930
+ 7 => 0,
1931
+ },
1932
+ "interactionIDs": Array [],
1933
+ "priorityLevel": "Immediate",
1934
+ "screenshot": null,
1935
+ "timestamp": 0,
1936
+}
1937
+`;
1938
+
1939
+exports[`ProfilingCache should record when props/state/hooks change: CommitDetails commitIndex: 1 1`] = `
1940
+Object {
1941
+ "changeDescriptions": Map {
1942
+ 5 => Object {
1943
+ "context": null,
1944
+ "didHooksChange": true,
1945
+ "props": Array [
1946
+ "count",
1947
+ ],
1948
+ "state": null,
1949
+ },
1950
+ 4 => Object {
1951
+ "context": true,
1952
+ "didHooksChange": false,
1953
+ "props": Array [],
1954
+ "state": null,
1955
+ },
1956
+ 7 => Object {
1957
+ "context": null,
1958
+ "didHooksChange": true,
1959
+ "props": Array [
1960
+ "count",
1961
+ ],
1962
+ "state": null,
1963
+ },
1964
+ 6 => Object {
1965
+ "context": Array [
1966
+ "count",
1967
+ ],
1968
+ "didHooksChange": false,
1969
+ "props": Array [],
1970
+ "state": null,
1971
+ },
1972
+ 2 => Object {
1973
+ "context": null,
1974
+ "didHooksChange": false,
1975
+ "props": Array [],
1976
+ "state": Array [
1977
+ "count",
1978
+ ],
1979
+ },
1980
+ },
1981
+ "duration": 0,
1982
+ "fiberActualDurations": Map {
1983
+ 5 => 0,
1984
+ 4 => 0,
1985
+ 7 => 0,
1986
+ 6 => 0,
1987
+ 3 => 0,
1988
+ 2 => 0,
1989
+ },
1990
+ "fiberSelfDurations": Map {
1991
+ 5 => 0,
1992
+ 4 => 0,
1993
+ 7 => 0,
1994
+ 6 => 0,
1995
+ 3 => 0,
1996
+ 2 => 0,
1997
+ },
1998
+ "interactionIDs": Array [],
1999
+ "priorityLevel": "Immediate",
2000
+ "screenshot": null,
2001
+ "timestamp": 0,
2002
+}
2003
+`;
2004
+
2005
+exports[`ProfilingCache should record when props/state/hooks change: imported data 1`] = `
2006
+Object {
2007
+ "dataForRoots": Array [
2008
+ Object {
2009
+ "commitData": Array [
2010
+ Object {
2011
+ "changeDescriptions": Array [],
2012
+ "duration": 0,
2013
+ "fiberActualDurations": Array [
2014
+ Array [
2015
+ 1,
2016
+ 0,
2017
+ ],
2018
+ Array [
2019
+ 2,
2020
+ 0,
2021
+ ],
2022
+ Array [
2023
+ 3,
2024
+ 0,
2025
+ ],
2026
+ Array [
2027
+ 4,
2028
+ 0,
2029
+ ],
2030
+ Array [
2031
+ 5,
2032
+ 0,
2033
+ ],
2034
+ Array [
2035
+ 6,
2036
+ 0,
2037
+ ],
2038
+ Array [
2039
+ 7,
2040
+ 0,
2041
+ ],
2042
+ ],
2043
+ "fiberSelfDurations": Array [
2044
+ Array [
2045
+ 1,
2046
+ 0,
2047
+ ],
2048
+ Array [
2049
+ 2,
2050
+ 0,
2051
+ ],
2052
+ Array [
2053
+ 3,
2054
+ 0,
2055
+ ],
2056
+ Array [
2057
+ 4,
2058
+ 0,
2059
+ ],
2060
+ Array [
2061
+ 5,
2062
+ 0,
2063
+ ],
2064
+ Array [
2065
+ 6,
2066
+ 0,
2067
+ ],
2068
+ Array [
2069
+ 7,
2070
+ 0,
2071
+ ],
2072
+ ],
2073
+ "interactionIDs": Array [],
2074
+ "priorityLevel": "Immediate",
2075
+ "screenshot": null,
2076
+ "timestamp": 0,
2077
+ },
2078
+ Object {
2079
+ "changeDescriptions": Array [
2080
+ Array [
2081
+ 5,
2082
+ Object {
2083
+ "context": null,
2084
+ "didHooksChange": true,
2085
+ "props": Array [
2086
+ "count",
2087
+ ],
2088
+ "state": null,
2089
+ },
2090
+ ],
2091
+ Array [
2092
+ 4,
2093
+ Object {
2094
+ "context": true,
2095
+ "didHooksChange": false,
2096
+ "props": Array [],
2097
+ "state": null,
2098
+ },
2099
+ ],
2100
+ Array [
2101
+ 7,
2102
+ Object {
2103
+ "context": null,
2104
+ "didHooksChange": true,
2105
+ "props": Array [
2106
+ "count",
2107
+ ],
2108
+ "state": null,
2109
+ },
2110
+ ],
2111
+ Array [
2112
+ 6,
2113
+ Object {
2114
+ "context": Array [
2115
+ "count",
2116
+ ],
2117
+ "didHooksChange": false,
2118
+ "props": Array [],
2119
+ "state": null,
2120
+ },
2121
+ ],
2122
+ Array [
2123
+ 2,
2124
+ Object {
2125
+ "context": null,
2126
+ "didHooksChange": false,
2127
+ "props": Array [],
2128
+ "state": Array [
2129
+ "count",
2130
+ ],
2131
+ },
2132
+ ],
2133
+ ],
2134
+ "duration": 0,
2135
+ "fiberActualDurations": Array [
2136
+ Array [
2137
+ 5,
2138
+ 0,
2139
+ ],
2140
+ Array [
2141
+ 4,
2142
+ 0,
2143
+ ],
2144
+ Array [
2145
+ 7,
2146
+ 0,
2147
+ ],
2148
+ Array [
2149
+ 6,
2150
+ 0,
2151
+ ],
2152
+ Array [
2153
+ 3,
2154
+ 0,
2155
+ ],
2156
+ Array [
2157
+ 2,
2158
+ 0,
2159
+ ],
2160
+ ],
2161
+ "fiberSelfDurations": Array [
2162
+ Array [
2163
+ 5,
2164
+ 0,
2165
+ ],
2166
+ Array [
2167
+ 4,
2168
+ 0,
2169
+ ],
2170
+ Array [
2171
+ 7,
2172
+ 0,
2173
+ ],
2174
+ Array [
2175
+ 6,
2176
+ 0,
2177
+ ],
2178
+ Array [
2179
+ 3,
2180
+ 0,
2181
+ ],
2182
+ Array [
2183
+ 2,
2184
+ 0,
2185
+ ],
2186
+ ],
2187
+ "interactionIDs": Array [],
2188
+ "priorityLevel": "Immediate",
2189
+ "screenshot": null,
2190
+ "timestamp": 0,
2191
+ },
2192
+ ],
2193
+ "displayName": "LegacyContextProvider",
2194
+ "initialTreeBaseDurations": Array [],
2195
+ "interactionCommits": Array [],
2196
+ "interactions": Array [],
2197
+ "operations": Array [
2198
+ Array [
2199
+ 1,
2200
+ 1,
2201
+ 110,
2202
+ 21,
2203
+ 76,
2204
+ 101,
2205
+ 103,
2206
+ 97,
2207
+ 99,
2208
+ 121,
2209
+ 67,
2210
+ 111,
2211
+ 110,
2212
+ 116,
2213
+ 101,
2214
+ 120,
2215
+ 116,
2216
+ 80,
2217
+ 114,
2218
+ 111,
2219
+ 118,
2220
+ 105,
2221
+ 100,
2222
+ 101,
2223
+ 114,
2224
+ 16,
2225
+ 67,
2226
+ 111,
2227
+ 110,
2228
+ 116,
2229
+ 101,
2230
+ 120,
2231
+ 116,
2232
+ 46,
2233
+ 80,
2234
+ 114,
2235
+ 111,
2236
+ 118,
2237
+ 105,
2238
+ 100,
2239
+ 101,
2240
+ 114,
2241
+ 21,
2242
+ 77,
2243
+ 111,
2244
+ 100,
2245
+ 101,
2246
+ 114,
2247
+ 110,
2248
+ 67,
2249
+ 111,
2250
+ 110,
2251
+ 116,
2252
+ 101,
2253
+ 120,
2254
+ 116,
2255
+ 67,
2256
+ 111,
2257
+ 110,
2258
+ 115,
2259
+ 117,
2260
+ 109,
2261
+ 101,
2262
+ 114,
2263
+ 26,
2264
+ 70,
2265
+ 117,
2266
+ 110,
2267
+ 99,
2268
+ 116,
2269
+ 105,
2270
+ 111,
2271
+ 110,
2272
+ 67,
2273
+ 111,
2274
+ 109,
2275
+ 112,
2276
+ 111,
2277
+ 110,
2278
+ 101,
2279
+ 110,
2280
+ 116,
2281
+ 87,
2282
+ 105,
2283
+ 116,
2284
+ 104,
2285
+ 72,
2286
+ 111,
2287
+ 111,
2288
+ 107,
2289
+ 115,
2290
+ 21,
2291
+ 76,
2292
+ 101,
2293
+ 103,
2294
+ 97,
2295
+ 99,
2296
+ 121,
2297
+ 67,
2298
+ 111,
2299
+ 110,
2300
+ 116,
2301
+ 101,
2302
+ 120,
2303
+ 116,
2304
+ 67,
2305
+ 111,
2306
+ 110,
2307
+ 115,
2308
+ 117,
2309
+ 109,
2310
+ 101,
2311
+ 114,
2312
+ 1,
2313
+ 1,
2314
+ 11,
2315
+ 1,
2316
+ 1,
2317
+ 1,
2318
+ 2,
2319
+ 1,
2320
+ 1,
2321
+ 0,
2322
+ 1,
2323
+ 0,
2324
+ 4,
2325
+ 2,
2326
+ 0,
2327
+ 1,
2328
+ 3,
2329
+ 2,
2330
+ 2,
2331
+ 2,
2332
+ 2,
2333
+ 0,
2334
+ 4,
2335
+ 3,
2336
+ 0,
2337
+ 1,
2338
+ 4,
2339
+ 1,
2340
+ 3,
2341
+ 2,
2342
+ 3,
2343
+ 0,
2344
+ 4,
2345
+ 4,
2346
+ 0,
2347
+ 1,
2348
+ 5,
2349
+ 5,
2350
+ 4,
2351
+ 4,
2352
+ 4,
2353
+ 0,
2354
+ 4,
2355
+ 5,
2356
+ 0,
2357
+ 1,
2358
+ 6,
2359
+ 1,
2360
+ 3,
2361
+ 2,
2362
+ 5,
2363
+ 0,
2364
+ 4,
2365
+ 6,
2366
+ 0,
2367
+ 1,
2368
+ 7,
2369
+ 5,
2370
+ 6,
2371
+ 6,
2372
+ 4,
2373
+ 0,
2374
+ 4,
2375
+ 7,
2376
+ 0,
2377
+ ],
2378
+ Array [
2379
+ 1,
2380
+ 1,
2381
+ 0,
2382
+ ],
2383
+ ],
2384
+ "rootID": 1,
2385
+ "snapshots": Array [],
2386
+ },
2387
+ ],
2388
+ "version": 4,
2389
+}
2390
+`;
2391
+
2392
exports[`ProfilingCache should report every traced interaction: Interactions 1`] = `
2393
Array [
2394
Object {
@@ -1951,19 +2462,21 @@ Object {
2462
Array [
2463
3,
2464
Object {
2465
+ "context": null,
2466
"didHooksChange": false,
2467
"props": Array [],
1956
- "state": Array [],
2468
+ "state": null,
2469
},
2470
],
2471
Array [
2472
2,
2473
Object {
2474
+ "context": null,
2475
"didHooksChange": false,
2476
"props": Array [
2477
"count",
2478
],
1966
- "state": Array [],
2479
+ "state": null,
2480
},
2481
],
2482
],
src/__tests__/profilingCache-test.js
+106
@@ -5,6 +5,7 @@ import type Bridge from 'src/bridge';
5
import type Store from 'src/devtools/store';
6
7
describe('ProfilingCache', () => {
8
+ let PropTypes;
9
let React;
10
let ReactDOM;
11
let Scheduler;
@@ -23,6 +24,7 @@ describe('ProfilingCache', () => {
24
store.collapseNodesByDefault = false;
25
store.recordChangeDescriptions = true;
26
27
+ PropTypes = require('prop-types');
28
React = require('react');
29
ReactDOM = require('react-dom');
30
Scheduler = require('scheduler');
@@ -188,6 +190,110 @@ describe('ProfilingCache', () => {
190
}
191
});
192
193
+ it('should record when props/state/hooks change', () => {
194
+ let instance = null;
195
+
196
+ const ModernContext = React.createContext(0);
197
+
198
+ class LegacyContextProvider extends React.Component<
199
+ any,
200
+ {| count: number |}
201
+ > {
202
+ static childContextTypes = {
203
+ count: PropTypes.number,
204
+ };
205
+ state = { count: 0 };
206
+ getChildContext() {
207
+ return this.state;
208
+ }
209
+ render() {
210
+ instance = this;
211
+ return (
212
+ <ModernContext.Provider value={this.state.count}>
213
+ <React.Fragment>
214
+ <ModernContextConsumer />
215
+ <LegacyContextConsumer />
216
+ </React.Fragment>
217
+ </ModernContext.Provider>
218
+ );
219
+ }
220
+ }
221
+
222
+ const FunctionComponentWithHooks = ({ count }) => {
223
+ React.useMemo(() => count, [count]);
224
+ return null;
225
+ };
226
+
227
+ class ModernContextConsumer extends React.Component<any> {
228
+ static contextType = ModernContext;
229
+ render() {
230
+ return <FunctionComponentWithHooks count={this.context} />;
231
+ }
232
+ }
233
+
234
+ class LegacyContextConsumer extends React.Component<any> {
235
+ static contextTypes = {
236
+ count: PropTypes.number,
237
+ };
238
+ render() {
239
+ return <FunctionComponentWithHooks count={this.context.count} />;
240
+ }
241
+ }
242
+
243
+ const container = document.createElement('div');
244
+
245
+ utils.act(() => store.profilerStore.startProfiling());
246
+ utils.act(() => ReactDOM.render(<LegacyContextProvider />, container));
247
+ expect(instance).not.toBeNull();
248
+ utils.act(() => (instance: any).setState({ count: 1 }));
249
+ utils.act(() => store.profilerStore.stopProfiling());
250
+
251
+ const allCommitData = [];
252
+
253
+ function Validator({ commitIndex, previousCommitDetails, rootID }) {
254
+ const commitData = store.profilerStore.getCommitData(rootID, commitIndex);
255
+ if (previousCommitDetails != null) {
256
+ expect(commitData).toEqual(previousCommitDetails);
257
+ } else {
258
+ allCommitData.push(commitData);
259
+ expect(commitData).toMatchSnapshot(
260
+ `CommitDetails commitIndex: ${commitIndex}`
261
+ );
262
+ }
263
+ return null;
264
+ }
265
+
266
+ const rootID = store.roots[0];
267
+
268
+ for (let commitIndex = 0; commitIndex < 2; commitIndex++) {
269
+ utils.act(() => {
270
+ TestRenderer.create(
271
+ <Validator
272
+ commitIndex={commitIndex}
273
+ previousCommitDetails={null}
274
+ rootID={rootID}
275
+ />
276
+ );
277
+ });
278
+ }
279
+
280
+ expect(allCommitData).toHaveLength(2);
281
+
282
+ utils.exportImportHelper(bridge, store);
283
+
284
+ for (let commitIndex = 0; commitIndex < 2; commitIndex++) {
285
+ utils.act(() => {
286
+ TestRenderer.create(
287
+ <Validator
288
+ commitIndex={commitIndex}
289
+ previousCommitDetails={allCommitData[commitIndex]}
290
+ rootID={rootID}
291
+ />
292
+ );
293
+ });
294
+ }
295
+ });
296
+
297
it('should calculate a self duration based on actual children (not filtered children)', () => {
298
store.componentFilters = [utils.createDisplayNameFilter('^Parent$')];
299
src/backend/renderer.js
+101
-10
@@ -662,6 +662,7 @@ export function attach(
662
prevFiber.memoizedState,
663
nextFiber.memoizedState
664
),
665
+ context: getContextChangedKeys(nextFiber),
666
props: getChangedKeys(
667
prevFiber.memoizedProps,
668
nextFiber.memoizedProps
@@ -676,6 +677,88 @@ export function attach(
677
}
678
}
679
680
+ function updateContextsForFiber(fiber: Fiber) {
681
+ switch (getElementTypeForFiber(fiber)) {
682
+ case ElementTypeClass:
683
+ if (idToContextsMap !== null) {
684
+ const id = getFiberID(getPrimaryFiber(fiber));
685
+ const contexts = getContextsForFiber(fiber);
686
+ if (contexts !== null) {
687
+ idToContextsMap.set(id, contexts);
688
+ }
689
+ }
690
+ break;
691
+ default:
692
+ break;
693
+ }
694
+ }
695
+
696
+ const NO_CONTEXT = {};
697
+
698
+ function getContextsForFiber(fiber: Fiber): [Object, any] | null {
699
+ switch (getElementTypeForFiber(fiber)) {
700
+ case ElementTypeClass:
701
+ const instance = fiber.stateNode;
702
+ let legacyContext = NO_CONTEXT;
703
+ let modernContext = NO_CONTEXT;
704
+ if (instance != null) {
705
+ if (
706
+ instance.constructor &&
707
+ instance.constructor.contextType != null
708
+ ) {
709
+ modernContext = instance.context;
710
+ } else {
711
+ legacyContext = instance.context;
712
+ if (legacyContext && Object.keys(legacyContext).length === 0) {
713
+ legacyContext = NO_CONTEXT;
714
+ }
715
+ }
716
+ }
717
+ return [legacyContext, modernContext];
718
+ default:
719
+ return null;
720
+ }
721
+ }
722
+
723
+ function crawlToInitializeContextsMap(fiber: Fiber) {
724
+ updateContextsForFiber(fiber);
725
+ let current = fiber.child;
726
+ while (current !== null) {
727
+ crawlToInitializeContextsMap(current);
728
+ current = current.sibling;
729
+ }
730
+ }
731
+
732
+ function getContextChangedKeys(fiber: Fiber): null | boolean | Array<string> {
733
+ switch (getElementTypeForFiber(fiber)) {
734
+ case ElementTypeClass:
735
+ if (idToContextsMap !== null) {
736
+ const id = getFiberID(getPrimaryFiber(fiber));
737
+ const prevContexts = idToContextsMap.has(id)
738
+ ? idToContextsMap.get(id)
739
+ : null;
740
+ const nextContexts = getContextsForFiber(fiber);
741
+
742
+ if (prevContexts == null || nextContexts == null) {
743
+ return null;
744
+ }
745
+
746
+ const [prevLegacyContext, prevModernContext] = prevContexts;
747
+ const [nextLegacyContext, nextModernContext] = nextContexts;
748
+
749
+ if (nextLegacyContext !== NO_CONTEXT) {
750
+ return getChangedKeys(prevLegacyContext, nextLegacyContext);
751
+ } else if (nextModernContext !== NO_CONTEXT) {
752
+ return prevModernContext !== nextModernContext;
753
+ }
754
+ }
755
+ break;
756
+ default:
757
+ break;
758
+ }
759
+ return null;
760
+ }
761
+
762
function didHooksChange(prev: any, next: any): boolean {
763
if (next == null) {
764
return false;
@@ -701,11 +784,9 @@ export function attach(
784
return false;
785
}
786
704
- function getChangedKeys(prev: any, next: any): Array<string> {
705
- const keys = [];
706
-
707
- if (next == null) {
708
- return keys;
787
+ function getChangedKeys(prev: any, next: any): null | Array<string> {
788
+ if (prev == null || next == null) {
789
+ return null;
790
}
791
792
// We can't report anything meaningful for hooks changes.
@@ -715,16 +796,18 @@ export function attach(
796
next.hasOwnProperty('next') &&
797
next.hasOwnProperty('queue')
798
) {
718
- return keys;
799
+ return null;
800
}
801
721
- // TODO (change descriptions) This does not account for props that were added or removed.
722
- for (let key in prev) {
802
+ const keys = new Set([...Object.keys(prev), ...Object.keys(next)]);
803
+ const changedKeys = [];
804
+ for (let key of keys) {
805
if (prev[key] !== next[key]) {
724
- keys.push(key);
806
+ changedKeys.push(key);
807
}
808
}
727
- return keys;
809
+
810
+ return changedKeys;
811
}
812
813
// eslint-disable-next-line no-unused-vars
@@ -1139,6 +1222,8 @@ export function attach(
1222
if (changeDescription !== null) {
1223
metadata.changeDescriptions.set(id, changeDescription);
1224
}
1225
+
1226
+ updateContextsForFiber(fiber);
1227
}
1228
}
1229
}
@@ -2164,6 +2249,7 @@ export function attach(
2249
2250
let currentCommitProfilingMetadata: CommitProfilingData | null = null;
2251
let displayNamesByRootID: DisplayNamesByRootID | null = null;
2252
+ let idToContextsMap: Map<number, any> | null = null;
2253
let initialTreeBaseDurationsMap: Map<number, number> | null = null;
2254
let initialIDToRootMap: Map<number, number> | null = null;
2255
let isProfiling: boolean = false;
@@ -2284,6 +2370,7 @@ export function attach(
2370
displayNamesByRootID = new Map();
2371
initialTreeBaseDurationsMap = new Map(idToTreeBaseDurationMap);
2372
initialIDToRootMap = new Map(idToRootMap);
2373
+ idToContextsMap = new Map();
2374
2375
hook.getFiberRoots(rendererID).forEach(root => {
2376
const rootID = getFiberID(getPrimaryFiber(root.current));
@@ -2291,6 +2378,10 @@ export function attach(
2378
rootID,
2379
getDisplayNameForRoot(root.current)
2380
);
2381
+
2382
+ if (shouldRecordChangeDescriptions) {
2383
+ crawlToInitializeContextsMap(root.current);
2384
+ }
2385
});
2386
2387
isProfiling = true;
src/backend/types.js
+4
-3
@@ -112,11 +112,12 @@ export type ReactRenderer = {
112
currentDispatcherRef?: {| current: null | Dispatcher |},
113
};
114
115
-// TODO (change descriptions) Is it important to handle context?
115
+// TODO (change descriptions) Should we report changed hooks keys?
116
export type ChangeDescription = {|
117
+ context: Array<string> | boolean | null,
118
didHooksChange: boolean,
118
- props: Array<string>,
119
- state: Array<string>,
119
+ props: Array<string> | null,
120
+ state: Array<string> | null,
121
|};
122
123
export type CommitDataBackend = {|
src/devtools/views/Profiler/SidebarSelectedFiberInfo.js
+27
-8
@@ -104,21 +104,33 @@ function WhatChanged({
104
105
const changeDescription = changeDescriptions.get(fiberID);
106
if (changeDescription == null) {
107
- return null;
107
+ // This indicates that the component mounted during this commit
108
+ return (
109
+ <div className={styles.Content}>
110
+ <label className={styles.Label}>Why did this render?</label>
111
+ <div className={styles.WhatChangedItem}>
112
+ This is the first time the component rendered.
113
+ </div>
114
+ </div>
115
+ );
116
}
117
118
const changes = [];
119
+
120
if (changeDescription.didHooksChange) {
121
changes.push(
122
<div key="hooks" className={styles.WhatChangedItem}>
114
- • Hooks
123
+ • Hooks changed
124
</div>
125
);
126
}
118
- if (changeDescription.props.length !== 0) {
127
+ if (
128
+ changeDescription.props !== null &&
129
+ changeDescription.props.length !== 0
130
+ ) {
131
changes.push(
132
<div key="props" className={styles.WhatChangedItem}>
121
- • Props
133
+ • Props changed:
134
{changeDescription.props.map(key => (
135
<span key={key} className={styles.WhatChangedKey}>
136
{key}
@@ -127,10 +139,13 @@ function WhatChanged({
139
</div>
140
);
141
}
130
- if (changeDescription.state.length !== 0) {
142
+ if (
143
+ changeDescription.state !== null &&
144
+ changeDescription.state.length !== 0
145
+ ) {
146
changes.push(
147
<div key="state" className={styles.WhatChangedItem}>
133
- • State
148
+ • State changed:
149
{changeDescription.state.map(key => (
150
<span key={key} className={styles.WhatChangedKey}>
151
{key}
@@ -141,12 +156,16 @@ function WhatChanged({
156
}
157
158
if (changes.length === 0) {
144
- changes.push(<div className={styles.WhatChangedItem}>Nothing</div>);
159
+ changes.push(
160
+ <div key="nothing" className={styles.WhatChangedItem}>
161
+ The parent component rendered.
162
+ </div>
163
+ );
164
}
165
166
return (
167
<div className={styles.Content}>
149
- <label className={styles.Label}>What changed?</label>
168
+ <label className={styles.Label}>Why did this render?</label>
169
{changes}
170
</div>
171
);
src/devtools/views/Profiler/types.js
+4
-3
@@ -31,11 +31,12 @@ export type SnapshotNode = {|
31
type: ElementType,
32
|};
33
34
-// TODO (change descriptions) Is it important to handle context?
34
+// TODO (change descriptions) Should we report changed hooks keys?
35
export type ChangeDescription = {|
36
+ context: Array<string> | boolean | null,
37
didHooksChange: boolean,
37
- props: Array<string>,
38
- state: Array<string>,
38
+ props: Array<string> | null,
39
+ state: Array<string> | null,
40
|};
41
42
export type CommitDataFrontend = {|
src/devtools/views/Settings/Settings.js
+1
-1
@@ -174,7 +174,7 @@ function Settings(_: {||}) {
174
checked={recordChangeDescriptions}
175
onChange={updateRecordChangeDescriptions}
176
/>{' '}
177
- Record which props/state/hooks changed while profiling
177
+ Record why each component rendered while profiling.
178
</label>
179
180
{store.supportsCaptureScreenshots && (