[Fiber] String refs and owner tracking (#8099)
* Implement string refs using callback closures * Merge Fiber type to avoid Flow intersection bugs Still one remaining type error that I'm not sure how to fix * Fix Flow issue with an unsafe cast * Fix missing semicolon * Add a type import I missed earlier
Andrew Clark committed
Oct 26, 2016 at 06:37 UTC
d5eff3b0eb714679d41d39edc160b45d0e5c48af
8 files changed
+101
-24
src/isomorphic/classic/element/ReactCurrentOwner.js
+2
-1
@@ -13,6 +13,7 @@
13
'use strict';
14
15
import type { ReactInstance } from 'ReactInstanceType';
16
+import type { Fiber } from 'ReactFiber';
17
18
/**
19
* Keeps track of the current owner.
@@ -26,7 +27,7 @@ var ReactCurrentOwner = {
27
* @internal
28
* @type {ReactComponent}
29
*/
29
- current: (null: null | ReactInstance),
30
+ current: (null: null | ReactInstance | Fiber),
31
32
};
33
src/isomorphic/hooks/ReactComponentTreeHook.js
+4
-3
@@ -315,9 +315,10 @@ var ReactComponentTreeHook = {
315
}
316
317
var currentOwner = ReactCurrentOwner.current;
318
- var id = currentOwner && currentOwner._debugID;
319
-
320
- info += ReactComponentTreeHook.getStackAddendumByID(id);
318
+ if (currentOwner && typeof currentOwner._debugID === 'number') {
319
+ var id = currentOwner && currentOwner._debugID;
320
+ info += ReactComponentTreeHook.getStackAddendumByID(id);
321
+ }
322
return info;
323
},
324
src/renderers/native/findNodeHandle.js
+5
-1
@@ -18,6 +18,8 @@ var ReactInstanceMap = require('ReactInstanceMap');
18
var invariant = require('invariant');
19
var warning = require('warning');
20
21
+import type { ReactInstance } from 'ReactInstanceType';
22
+
23
/**
24
* ReactNative vs ReactWeb
25
* -----------------------
@@ -50,7 +52,8 @@ var warning = require('warning');
52
53
function findNodeHandle(componentOrHandle: any): ?number {
54
if (__DEV__) {
53
- var owner = ReactCurrentOwner.current;
55
+ // TODO: fix this unsafe cast to work with Fiber.
56
+ var owner = ((ReactCurrentOwner.current: any): ReactInstance | null);
57
if (owner !== null) {
58
warning(
59
owner._warnedAboutRefsInRender,
@@ -61,6 +64,7 @@ function findNodeHandle(componentOrHandle: any): ?number {
64
'componentDidUpdate instead.',
65
owner.getName() || 'A component'
66
);
67
+
68
owner._warnedAboutRefsInRender = true;
69
}
70
}
src/renderers/shared/fiber/ReactChildFiber.js
+32
-5
@@ -28,6 +28,7 @@ var ReactReifiedYield = require('ReactReifiedYield');
28
var ReactTypeOfSideEffect = require('ReactTypeOfSideEffect');
29
var ReactTypeOfWork = require('ReactTypeOfWork');
30
31
+var emptyObject = require('emptyObject');
32
var getIteratorFn = require('getIteratorFn');
33
34
const {
@@ -47,6 +48,7 @@ const {
48
const isArray = Array.isArray;
49
50
const {
51
+ ClassComponent,
52
HostText,
53
CoroutineComponent,
54
YieldComponent,
@@ -62,6 +64,31 @@ const {
64
Deletion,
65
} = ReactTypeOfSideEffect;
66
67
+function transferRef(current: ?Fiber, workInProgress: Fiber, element: ReactElement<any>) {
68
+ if (typeof element.ref === 'string') {
69
+ if (element._owner) {
70
+ const ownerFiber : ?Fiber = (element._owner : any);
71
+ if (ownerFiber && ownerFiber.tag === ClassComponent) {
72
+ const stringRef = element.ref;
73
+ // Check if previous string ref matches new string ref
74
+ if (current && current.ref && current.ref._stringRef === stringRef) {
75
+ workInProgress.ref = current.ref;
76
+ return;
77
+ }
78
+ const inst = ownerFiber.stateNode;
79
+ const ref = function(value) {
80
+ const refs = inst.refs === emptyObject ? (inst.refs = {}) : inst.refs;
81
+ refs[stringRef] = value;
82
+ };
83
+ ref._stringRef = stringRef;
84
+ workInProgress.ref = ref;
85
+ }
86
+ }
87
+ } else {
88
+ workInProgress.ref = element.ref;
89
+ }
90
+}
91
+
92
// This wrapper function exists because I expect to clone the code in each path
93
// to be able to optimize each path individually by branching early. This needs
94
// a compiler or we can do it manually. Helpers that don't need this branching
@@ -221,13 +248,13 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
248
if (current == null || current.type !== element.type) {
249
// Insert
250
const created = createFiberFromElement(element, priority);
224
- created.ref = element.ref;
251
+ transferRef(current, created, element);
252
created.return = returnFiber;
253
return created;
254
} else {
255
// Move based on index
256
const existing = useFiber(current, priority);
230
- existing.ref = element.ref;
257
+ transferRef(current, existing, element);
258
existing.pendingProps = element.props;
259
existing.return = returnFiber;
260
return existing;
@@ -319,7 +346,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
346
switch (newChild.$$typeof) {
347
case REACT_ELEMENT_TYPE: {
348
const created = createFiberFromElement(newChild, priority);
322
- created.ref = newChild.ref;
349
+ transferRef(null, created, newChild);
350
created.return = returnFiber;
351
return created;
352
}
@@ -653,7 +680,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
680
if (child.type === element.type) {
681
deleteRemainingChildren(returnFiber, child.sibling);
682
const existing = useFiber(child, priority);
656
- existing.ref = element.ref;
683
+ transferRef(child, existing, element);
684
existing.pendingProps = element.props;
685
existing.return = returnFiber;
686
return existing;
@@ -668,7 +695,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
695
}
696
697
const created = createFiberFromElement(element, priority);
671
- created.ref = element.ref;
698
+ transferRef(currentFirstChild, created, element);
699
created.return = returnFiber;
700
return created;
701
}
src/renderers/shared/fiber/ReactFiber.js
+14
-12
@@ -39,11 +39,18 @@ var {
39
NoEffect,
40
} = require('ReactTypeOfSideEffect');
41
42
-// An Instance is shared between all versions of a component. We can easily
43
-// break this out into a separate object to avoid copying so much to the
44
-// alternate versions of the tree. We put this on a single object for now to
45
-// minimize the number of objects created during the initial render.
46
-type Instance = {
42
+// A Fiber is work on a Component that needs to be done or was done. There can
43
+// be more than one per component.
44
+export type Fiber = {
45
+ // These first fields are conceptually members of an Instance. This used to
46
+ // be split into a separate type and intersected with the other Fiber fields,
47
+ // but until Flow fixes its intersection bugs, we've merged them into a
48
+ // single type.
49
+
50
+ // An Instance is shared between all versions of a component. We can easily
51
+ // break this out into a separate object to avoid copying so much to the
52
+ // alternate versions of the tree. We put this on a single object for now to
53
+ // minimize the number of objects created during the initial render.
54
55
// Tag identifying the type of fiber.
56
tag: TypeOfWork,
@@ -61,11 +68,7 @@ type Instance = {
68
// parent : Instance -> return The parent happens to be the same as the
69
// return fiber since we've merged the fiber and instance.
70
64
-};
65
-
66
-// A Fiber is work on a Component that needs to be done or was done. There can
67
-// be more than one per component.
68
-export type Fiber = Instance & {
71
+ // Remaining fields belong to Fiber
72
73
// The Fiber to return to after finishing processing this one.
74
// This is effectively the parent, but there can be multiple parents (two)
@@ -80,7 +83,7 @@ export type Fiber = Instance & {
83
84
// The ref last used to attach this node.
85
// I'll avoid adding an owner field for prod and model that as functions.
83
- ref: null | (handle : ?Object) => void,
86
+ ref: null | (((handle : ?Object) => void) & { _stringRef: ?string }),
87
88
// Input is the data coming into process this fiber. Arguments. Props.
89
pendingProps: any, // This type will be more specific once we overload the tag.
@@ -327,4 +330,3 @@ exports.createFiberFromYield = function(yieldNode : ReactYield, priorityLevel :
330
fiber.pendingProps = {};
331
return fiber;
332
};
330
-
src/renderers/shared/fiber/ReactFiberBeginWork.js
+17
-2
@@ -16,6 +16,7 @@ import type { ReactCoroutine } from 'ReactCoroutine';
16
import type { Fiber } from 'ReactFiber';
17
import type { HostConfig } from 'ReactFiberReconciler';
18
import type { PriorityLevel } from 'ReactPriorityLevel';
19
+import ReactCurrentOwner from 'ReactCurrentOwner';
20
21
var {
22
mountChildFibersInPlace,
@@ -151,7 +152,12 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>, s
152
}
153
}
154
154
- var nextChildren = fn(props);
155
+ if (__DEV__) {
156
+ ReactCurrentOwner.current = workInProgress;
157
+ var nextChildren = fn(props);
158
+ } else {
159
+ var nextChildren = fn(props);
160
+ }
161
reconcileChildren(current, workInProgress, nextChildren);
162
return workInProgress.child;
163
}
@@ -176,6 +182,7 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>, s
182
}
183
// Rerender
184
const instance = workInProgress.stateNode;
185
+ ReactCurrentOwner.current = workInProgress;
186
const nextChildren = instance.render();
187
reconcileChildren(current, workInProgress, nextChildren);
188
return workInProgress.child;
@@ -237,12 +244,20 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>, s
244
}
245
var fn = workInProgress.type;
246
var props = workInProgress.pendingProps;
240
- var value = fn(props);
247
+
248
+ if (__DEV__) {
249
+ ReactCurrentOwner.current = workInProgress;
250
+ var value = fn(props);
251
+ } else {
252
+ var value = fn(props);
253
+ }
254
+
255
if (typeof value === 'object' && value && typeof value.render === 'function') {
256
// Proceed under the assumption that this is a class instance
257
workInProgress.tag = ClassComponent;
258
adoptClassInstance(workInProgress, value);
259
mountClassInstance(workInProgress);
260
+ ReactCurrentOwner.current = workInProgress;
261
value = value.render();
262
} else {
263
// Proceed under the assumption that this is a functional component
src/renderers/shared/fiber/ReactFiberScheduler.js
+3
@@ -20,6 +20,7 @@ import type { PriorityLevel } from 'ReactPriorityLevel';
20
var ReactFiberBeginWork = require('ReactFiberBeginWork');
21
var ReactFiberCompleteWork = require('ReactFiberCompleteWork');
22
var ReactFiberCommitWork = require('ReactFiberCommitWork');
23
+var ReactCurrentOwner = require('ReactCurrentOwner');
24
25
var { cloneFiber } = require('ReactFiber');
26
@@ -306,6 +307,8 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
307
}
308
}
309
310
+ ReactCurrentOwner.current = null;
311
+
312
return next;
313
}
314
src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js
+24
@@ -1072,4 +1072,28 @@ describe('ReactIncrementalSideEffects', () => {
1072
// TODO: Test that mounts, updates, refs, unmounts and deletions happen in the
1073
// expected way for aborted and resumed render life-cycles.
1074
1075
+ it('supports string refs', () => {
1076
+ var fooInstance = null;
1077
+
1078
+ class Bar extends React.Component {
1079
+ componentDidMount() {
1080
+ this.test = 'test';
1081
+ }
1082
+ render() {
1083
+ return <div />;
1084
+ }
1085
+ }
1086
+
1087
+ class Foo extends React.Component {
1088
+ render() {
1089
+ fooInstance = this;
1090
+ return <Bar ref="bar" />;
1091
+ }
1092
+ }
1093
+
1094
+ ReactNoop.render(<Foo />);
1095
+ ReactNoop.flush();
1096
+
1097
+ expect(fooInstance.refs.bar.test).toEqual('test');
1098
+ });
1099
});