@samitouri / QOS-React / commits / ea494a2c10

Type ReactComponentTreeHook (#7504)

For this one, I wanted to type a non-trivial piece of the codebase and ran into the fact that we do not have types for ReactElement nor ReactInstance, so I had to create them. I'll add comments inline

Christopher Chedeau committed Aug 25, 2016 at 15:27 UTC ea494a2c1082c705ef79d0b73516aa6923a4c75a
3 files changed +199 -121
src/isomorphic/classic/element/ReactElementType.js new
+37
@@ -0,0 +1,37 @@
1 +/**
2 + * Copyright 2016-present, Facebook, Inc.
3 + * All rights reserved.
4 + *
5 + * This source code is licensed under the BSD-style license found in the
6 + * LICENSE file in the root directory of this source tree. An additional grant
7 + * of patent rights can be found in the PATENTS file in the same directory.
8 + *
9 + * @flow
10 + * @providesModule ReactElementType
11 + */
12 +
13 +'use strict';
14 +
15 +import type { ReactInstance } from 'ReactInstanceType';
16 +
17 +export type Source = {
18 + fileName: string,
19 + lineNumber: number,
20 +};
21 +
22 +export type ReactElement = {
23 + $$typeof: any,
24 + type: any,
25 + key: any,
26 + ref: any,
27 + props: any,
28 + _owner: ReactInstance,
29 +
30 + // __DEV__
31 + _store: {
32 + validated: bool,
33 + },
34 + _self: ReactElement,
35 + _shadowChildren: any,
36 + _source: Source,
37 +};
src/isomorphic/hooks/ReactComponentTreeHook.js
+125 -121
@@ -6,6 +6,7 @@
6 * LICENSE file in the root directory of this source tree. An additional grant
7 * of patent rights can be found in the PATENTS file in the same directory.
8 *
9 + * @flow
10 * @providesModule ReactComponentTreeHook
11 */
12
@@ -16,6 +17,9 @@ var ReactCurrentOwner = require('ReactCurrentOwner');
17 var invariant = require('invariant');
18 var warning = require('warning');
19
20 +import type { ReactElement, Source } from 'ReactElementType';
21 +import type { DebugID } from 'ReactInstanceType';
22 +
23 function isNative(fn) {
24 // Based on isNative() from Lodash
25 var funcToString = Function.prototype.toString;
@@ -39,6 +43,24 @@ function isNative(fn) {
43 }
44 }
45
46 +type Item = {
47 + element: ReactElement,
48 + parentID: DebugID,
49 + text: ?string,
50 + childIDs: Array<DebugID>,
51 + isMounted: bool,
52 + updateCount: number,
53 +};
54 +
55 +declare function getItem(id: DebugID): ?Item;
56 +declare function removeItem(id: DebugID): void;
57 +declare function setItem(id: DebugID, item: Item): void;
58 +declare function getItemIDs(): Array<DebugID>;
59 +
60 +declare function addRoot(id: DebugID): void;
61 +declare function removeRoot(id: DebugID): void;
62 +declare function getRootIDs(): Array<DebugID>;
63 +
64 var canUseCollections = (
65 // Array.from
66 typeof Array.from === 'function' &&
@@ -58,112 +80,88 @@ var canUseCollections = (
80 isNative(Set.prototype.keys)
81 );
82
61 -var itemMap;
62 -var rootIDSet;
83 +if (canUseCollections) {
84 + var itemMap = new Map();
85 + var rootIDSet = new Set();
86
64 -var itemByKey;
65 -var rootByKey;
87 + var setItem = function(id, item) {
88 + itemMap.set(id, item);
89 + };
90 + var getItem = function(id) {
91 + return itemMap.get(id);
92 + };
93 + var removeItem = function(id) {
94 + itemMap.delete(id);
95 + };
96 + var getItemIDs = function() {
97 + return Array.from(itemMap.keys());
98 + };
99
67 -if (canUseCollections) {
68 - itemMap = new Map();
69 - rootIDSet = new Set();
70 -} else {
71 - itemByKey = {};
72 - rootByKey = {};
73 -}
100 + var addRoot = function(id) {
101 + rootIDSet.add(id);
102 + };
103 + var removeRoot = function(id) {
104 + rootIDSet.delete(id);
105 + };
106 + var getRootIDs = function() {
107 + return Array.from(rootIDSet.keys());
108 + };
109
75 -var unmountedIDs = [];
110 +} else {
111 + var itemByKey = {};
112 + var rootByKey = {};
113
77 -// Use non-numeric keys to prevent V8 performance issues:
78 -// https://github.com/facebook/react/pull/7232
79 -function getKeyFromID(id) {
80 - return '.' + id;
81 -}
82 -function getIDFromKey(key) {
83 - return parseInt(key.substr(1), 10);
84 -}
114 + // Use non-numeric keys to prevent V8 performance issues:
115 + // https://github.com/facebook/react/pull/7232
116 + function getKeyFromID(id: DebugID): string {
117 + return '.' + id;
118 + }
119 + function getIDFromKey(key: string): DebugID {
120 + return parseInt(key.substr(1), 10);
121 + }
122
86 -function get(id) {
87 - if (canUseCollections) {
88 - return itemMap.get(id);
89 - } else {
123 + var setItem = function(id, item) {
124 + var key = getKeyFromID(id);
125 + itemByKey[key] = item;
126 + };
127 + var getItem = function(id) {
128 var key = getKeyFromID(id);
129 return itemByKey[key];
92 - }
93 -}
94 -
95 -function remove(id) {
96 - if (canUseCollections) {
97 - itemMap.delete(id);
98 - } else {
130 + };
131 + var removeItem = function(id) {
132 var key = getKeyFromID(id);
133 delete itemByKey[key];
101 - }
102 -}
103 -
104 -function create(id, element, parentID) {
105 - var item = {
106 - element,
107 - parentID,
108 - text: null,
109 - childIDs: [],
110 - isMounted: false,
111 - updateCount: 0,
134 + };
135 + var getItemIDs = function() {
136 + return Object.keys(itemByKey).map(getIDFromKey);
137 };
138
114 - if (canUseCollections) {
115 - itemMap.set(id, item);
116 - } else {
117 - var key = getKeyFromID(id);
118 - itemByKey[key] = item;
119 - }
120 -}
121 -
122 -function addRoot(id) {
123 - if (canUseCollections) {
124 - rootIDSet.add(id);
125 - } else {
139 + var addRoot = function(id) {
140 var key = getKeyFromID(id);
141 rootByKey[key] = true;
128 - }
129 -}
130 -
131 -function removeRoot(id) {
132 - if (canUseCollections) {
133 - rootIDSet.delete(id);
134 - } else {
142 + };
143 + var removeRoot = function(id) {
144 var key = getKeyFromID(id);
145 delete rootByKey[key];
137 - }
138 -}
139 -
140 -function getRegisteredIDs() {
141 - if (canUseCollections) {
142 - return Array.from(itemMap.keys());
143 - } else {
144 - return Object.keys(itemByKey).map(getIDFromKey);
145 - }
146 -}
147 -
148 -function getRootIDs() {
149 - if (canUseCollections) {
150 - return Array.from(rootIDSet.keys());
151 - } else {
146 + };
147 + var getRootIDs = function() {
148 return Object.keys(rootByKey).map(getIDFromKey);
153 - }
149 + };
150 }
151
152 +var unmountedIDs: Array<DebugID> = [];
153 +
154 function purgeDeep(id) {
157 - var item = get(id);
155 + var item = getItem(id);
156 if (item) {
157 var {childIDs} = item;
160 - remove(id);
158 + removeItem(id);
159 childIDs.forEach(purgeDeep);
160 }
161 }
162
163 function describeComponentFrame(name, source, ownerName) {
166 - return '\n in ' + name + (
164 + return '\n in ' + (name || 'Unknown') + (
165 source ?
166 ' (at ' + source.fileName.replace(/^.*[\\\/]/, '') + ':' +
167 source.lineNumber + ')' :
@@ -173,7 +171,7 @@ function describeComponentFrame(name, source, ownerName) {
171 );
172 }
173
176 -function getDisplayName(element) {
174 +function getDisplayName(element: ?ReactElement): string {
175 if (element == null) {
176 return '#empty';
177 } else if (typeof element === 'string' || typeof element === 'number') {
@@ -185,7 +183,7 @@ function getDisplayName(element) {
183 }
184 }
185
188 -function describeID(id) {
186 +function describeID(id: DebugID): string {
187 var name = ReactComponentTreeHook.getDisplayName(id);
188 var element = ReactComponentTreeHook.getElement(id);
189 var ownerID = ReactComponentTreeHook.getOwnerID(id);
@@ -203,13 +201,14 @@ function describeID(id) {
201 }
202
203 var ReactComponentTreeHook = {
206 - onSetChildren(id, nextChildIDs) {
207 - var item = get(id);
204 + onSetChildren(id: DebugID, nextChildIDs: Array<DebugID>): void {
205 + var item = getItem(id);
206 + invariant(item, 'Item must have been set');
207 item.childIDs = nextChildIDs;
208
209 for (var i = 0; i < nextChildIDs.length; i++) {
210 var nextChildID = nextChildIDs[i];
212 - var nextChild = get(nextChildID);
211 + var nextChild = getItem(nextChildID);
212 invariant(
213 nextChild,
214 'Expected hook events to fire for the child ' +
@@ -231,7 +230,7 @@ var ReactComponentTreeHook = {
230 nextChild.parentID = id;
231 // TODO: This shouldn't be necessary but mounting a new root during in
232 // componentWillMount currently causes not-yet-mounted components to
234 - // be purged from our tree data so their parent ID is missing.
233 + // be purged from our tree data so their parent id is missing.
234 }
235 invariant(
236 nextChild.parentID === id,
@@ -244,12 +243,20 @@ var ReactComponentTreeHook = {
243 }
244 },
245
247 - onBeforeMountComponent(id, element, parentID) {
248 - create(id, element, parentID);
246 + onBeforeMountComponent(id: DebugID, element: ReactElement, parentID: DebugID): void {
247 + var item = {
248 + element,
249 + parentID,
250 + text: null,
251 + childIDs: [],
252 + isMounted: false,
253 + updateCount: 0,
254 + };
255 + setItem(id, item);
256 },
257
251 - onBeforeUpdateComponent(id, element) {
252 - var item = get(id);
258 + onBeforeUpdateComponent(id: DebugID, element: ReactElement): void {
259 + var item = getItem(id);
260 if (!item || !item.isMounted) {
261 // We may end up here as a result of setState() in componentWillUnmount().
262 // In this case, ignore the element.
@@ -258,8 +265,9 @@ var ReactComponentTreeHook = {
265 item.element = element;
266 },
267
261 - onMountComponent(id) {
262 - var item = get(id);
268 + onMountComponent(id: DebugID): void {
269 + var item = getItem(id);
270 + invariant(item, 'Item must have been set');
271 item.isMounted = true;
272 var isRoot = item.parentID === 0;
273 if (isRoot) {
@@ -267,8 +275,8 @@ var ReactComponentTreeHook = {
275 }
276 },
277
270 - onUpdateComponent(id) {
271 - var item = get(id);
278 + onUpdateComponent(id: DebugID): void {
279 + var item = getItem(id);
280 if (!item || !item.isMounted) {
281 // We may end up here as a result of setState() in componentWillUnmount().
282 // In this case, ignore the element.
@@ -277,8 +285,8 @@ var ReactComponentTreeHook = {
285 item.updateCount++;
286 },
287
280 - onUnmountComponent(id) {
281 - var item = get(id);
288 + onUnmountComponent(id: DebugID): void {
289 + var item = getItem(id);
290 if (item) {
291 // We need to check if it exists.
292 // `item` might not exist if it is inside an error boundary, and a sibling
@@ -294,7 +302,7 @@ var ReactComponentTreeHook = {
302 unmountedIDs.push(id);
303 },
304
297 - purgeUnmountedComponents() {
305 + purgeUnmountedComponents(): void {
306 if (ReactComponentTreeHook._preventPurging) {
307 // Should only be used for testing.
308 return;
@@ -307,21 +315,18 @@ var ReactComponentTreeHook = {
315 unmountedIDs.length = 0;
316 },
317
310 - isMounted(id) {
311 - var item = get(id);
318 + isMounted(id: DebugID): bool {
319 + var item = getItem(id);
320 return item ? item.isMounted : false;
321 },
322
315 - getCurrentStackAddendum(topElement) {
323 + getCurrentStackAddendum(topElement: ?ReactElement): string {
324 var info = '';
325 if (topElement) {
318 - var type = topElement.type;
319 - var name = typeof type === 'function' ?
320 - type.displayName || type.name :
321 - type;
326 + var name = getDisplayName(topElement);
327 var owner = topElement._owner;
328 info += describeComponentFrame(
324 - name || 'Unknown',
329 + name,
330 topElement._source,
331 owner && owner.getName()
332 );
@@ -334,7 +339,7 @@ var ReactComponentTreeHook = {
339 return info;
340 },
341
337 - getStackAddendumByID(id) {
342 + getStackAddendumByID(id: ?DebugID): string {
343 var info = '';
344 while (id) {
345 info += describeID(id);
@@ -343,12 +348,12 @@ var ReactComponentTreeHook = {
348 return info;
349 },
350
346 - getChildIDs(id) {
347 - var item = get(id);
351 + getChildIDs(id: DebugID): Array<DebugID> {
352 + var item = getItem(id);
353 return item ? item.childIDs : [];
354 },
355
351 - getDisplayName(id) {
356 + getDisplayName(id: DebugID): ?string {
357 var element = ReactComponentTreeHook.getElement(id);
358 if (!element) {
359 return null;
@@ -356,12 +361,12 @@ var ReactComponentTreeHook = {
361 return getDisplayName(element);
362 },
363
359 - getElement(id) {
360 - var item = get(id);
364 + getElement(id: DebugID): ?ReactElement {
365 + var item = getItem(id);
366 return item ? item.element : null;
367 },
368
364 - getOwnerID(id) {
369 + getOwnerID(id: DebugID): ?DebugID {
370 var element = ReactComponentTreeHook.getElement(id);
371 if (!element || !element._owner) {
372 return null;
@@ -369,19 +374,19 @@ var ReactComponentTreeHook = {
374 return element._owner._debugID;
375 },
376
372 - getParentID(id) {
373 - var item = get(id);
377 + getParentID(id: DebugID): ?DebugID {
378 + var item = getItem(id);
379 return item ? item.parentID : null;
380 },
381
377 - getSource(id) {
378 - var item = get(id);
382 + getSource(id: DebugID): ?Source {
383 + var item = getItem(id);
384 var element = item ? item.element : null;
385 var source = element != null ? element._source : null;
386 return source;
387 },
388
384 - getText(id) {
389 + getText(id: DebugID): ?string {
390 var element = ReactComponentTreeHook.getElement(id);
391 if (typeof element === 'string') {
392 return element;
@@ -392,14 +397,13 @@ var ReactComponentTreeHook = {
397 }
398 },
399
395 - getUpdateCount(id) {
396 - var item = get(id);
400 + getUpdateCount(id: DebugID): number {
401 + var item = getItem(id);
402 return item ? item.updateCount : 0;
403 },
404
400 - getRegisteredIDs,
401 -
405 getRootIDs,
406 + getRegisteredIDs: getItemIDs,
407 };
408
409 module.exports = ReactComponentTreeHook;
src/renderers/shared/stack/reconciler/ReactInstanceType.js new
+37
@@ -0,0 +1,37 @@
1 + /**
2 + * Copyright 2016-present, Facebook, Inc.
3 + * All rights reserved.
4 + *
5 + * This source code is licensed under the BSD-style license found in the
6 + * LICENSE file in the root directory of this source tree. An additional grant
7 + * of patent rights can be found in the PATENTS file in the same directory.
8 + *
9 + * @flow
10 + * @providesModule ReactInstanceType
11 + */
12 +
13 +'use strict';
14 +
15 +export type DebugID = number;
16 +
17 +export type ReactInstance = {
18 + // ReactCompositeComponent
19 + mountComponent: any,
20 + performInitialMountWithErrorHandling: any,
21 + performInitialMount: any,
22 + getHostNode: any,
23 + unmountComponent: any,
24 + receiveComponent: any,
25 + performUpdateIfNecessary: any,
26 + updateComponent: any,
27 + attachRef: any,
28 + detachRef: any,
29 + getName: () => string,
30 + getPublicInstance: any,
31 +
32 + // instantiateReactComponent
33 + _mountIndex: number,
34 + _mountImage: any,
35 + // __DEV__
36 + _debugID: DebugID,
37 +};