@samitouri / QOS-React / commits / d5eff3b0eb

[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 });