Skip to content

Commit d5eff3b

Browse files
acdlitegaearon
authored andcommitted
[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
1 parent ce2dee3 commit d5eff3b

File tree

8 files changed

+101
-24
lines changed

8 files changed

+101
-24
lines changed

src/isomorphic/classic/element/ReactCurrentOwner.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
'use strict';
1414

1515
import type { ReactInstance } from 'ReactInstanceType';
16+
import type { Fiber } from 'ReactFiber';
1617

1718
/**
1819
* Keeps track of the current owner.
@@ -26,7 +27,7 @@ var ReactCurrentOwner = {
2627
* @internal
2728
* @type {ReactComponent}
2829
*/
29-
current: (null: null | ReactInstance),
30+
current: (null: null | ReactInstance | Fiber),
3031

3132
};
3233

src/isomorphic/hooks/ReactComponentTreeHook.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -315,9 +315,10 @@ var ReactComponentTreeHook = {
315315
}
316316

317317
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+
}
321322
return info;
322323
},
323324

src/renderers/native/findNodeHandle.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ var ReactInstanceMap = require('ReactInstanceMap');
1818
var invariant = require('invariant');
1919
var warning = require('warning');
2020

21+
import type { ReactInstance } from 'ReactInstanceType';
22+
2123
/**
2224
* ReactNative vs ReactWeb
2325
* -----------------------
@@ -50,7 +52,8 @@ var warning = require('warning');
5052

5153
function findNodeHandle(componentOrHandle: any): ?number {
5254
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);
5457
if (owner !== null) {
5558
warning(
5659
owner._warnedAboutRefsInRender,
@@ -61,6 +64,7 @@ function findNodeHandle(componentOrHandle: any): ?number {
6164
'componentDidUpdate instead.',
6265
owner.getName() || 'A component'
6366
);
67+
6468
owner._warnedAboutRefsInRender = true;
6569
}
6670
}

src/renderers/shared/fiber/ReactChildFiber.js

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ var ReactReifiedYield = require('ReactReifiedYield');
2828
var ReactTypeOfSideEffect = require('ReactTypeOfSideEffect');
2929
var ReactTypeOfWork = require('ReactTypeOfWork');
3030

31+
var emptyObject = require('emptyObject');
3132
var getIteratorFn = require('getIteratorFn');
3233

3334
const {
@@ -47,6 +48,7 @@ const {
4748
const isArray = Array.isArray;
4849

4950
const {
51+
ClassComponent,
5052
HostText,
5153
CoroutineComponent,
5254
YieldComponent,
@@ -62,6 +64,31 @@ const {
6264
Deletion,
6365
} = ReactTypeOfSideEffect;
6466

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+
6592
// This wrapper function exists because I expect to clone the code in each path
6693
// to be able to optimize each path individually by branching early. This needs
6794
// a compiler or we can do it manually. Helpers that don't need this branching
@@ -221,13 +248,13 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
221248
if (current == null || current.type !== element.type) {
222249
// Insert
223250
const created = createFiberFromElement(element, priority);
224-
created.ref = element.ref;
251+
transferRef(current, created, element);
225252
created.return = returnFiber;
226253
return created;
227254
} else {
228255
// Move based on index
229256
const existing = useFiber(current, priority);
230-
existing.ref = element.ref;
257+
transferRef(current, existing, element);
231258
existing.pendingProps = element.props;
232259
existing.return = returnFiber;
233260
return existing;
@@ -319,7 +346,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
319346
switch (newChild.$$typeof) {
320347
case REACT_ELEMENT_TYPE: {
321348
const created = createFiberFromElement(newChild, priority);
322-
created.ref = newChild.ref;
349+
transferRef(null, created, newChild);
323350
created.return = returnFiber;
324351
return created;
325352
}
@@ -653,7 +680,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
653680
if (child.type === element.type) {
654681
deleteRemainingChildren(returnFiber, child.sibling);
655682
const existing = useFiber(child, priority);
656-
existing.ref = element.ref;
683+
transferRef(child, existing, element);
657684
existing.pendingProps = element.props;
658685
existing.return = returnFiber;
659686
return existing;
@@ -668,7 +695,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
668695
}
669696

670697
const created = createFiberFromElement(element, priority);
671-
created.ref = element.ref;
698+
transferRef(currentFirstChild, created, element);
672699
created.return = returnFiber;
673700
return created;
674701
}

src/renderers/shared/fiber/ReactFiber.js

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,18 @@ var {
3939
NoEffect,
4040
} = require('ReactTypeOfSideEffect');
4141

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.
4754

4855
// Tag identifying the type of fiber.
4956
tag: TypeOfWork,
@@ -61,11 +68,7 @@ type Instance = {
6168
// parent : Instance -> return The parent happens to be the same as the
6269
// return fiber since we've merged the fiber and instance.
6370

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
6972

7073
// The Fiber to return to after finishing processing this one.
7174
// This is effectively the parent, but there can be multiple parents (two)
@@ -80,7 +83,7 @@ export type Fiber = Instance & {
8083

8184
// The ref last used to attach this node.
8285
// 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 }),
8487

8588
// Input is the data coming into process this fiber. Arguments. Props.
8689
pendingProps: any, // This type will be more specific once we overload the tag.
@@ -327,4 +330,3 @@ exports.createFiberFromYield = function(yieldNode : ReactYield, priorityLevel :
327330
fiber.pendingProps = {};
328331
return fiber;
329332
};
330-

src/renderers/shared/fiber/ReactFiberBeginWork.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import type { ReactCoroutine } from 'ReactCoroutine';
1616
import type { Fiber } from 'ReactFiber';
1717
import type { HostConfig } from 'ReactFiberReconciler';
1818
import type { PriorityLevel } from 'ReactPriorityLevel';
19+
import ReactCurrentOwner from 'ReactCurrentOwner';
1920

2021
var {
2122
mountChildFibersInPlace,
@@ -151,7 +152,12 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>, s
151152
}
152153
}
153154

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+
}
155161
reconcileChildren(current, workInProgress, nextChildren);
156162
return workInProgress.child;
157163
}
@@ -176,6 +182,7 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>, s
176182
}
177183
// Rerender
178184
const instance = workInProgress.stateNode;
185+
ReactCurrentOwner.current = workInProgress;
179186
const nextChildren = instance.render();
180187
reconcileChildren(current, workInProgress, nextChildren);
181188
return workInProgress.child;
@@ -237,12 +244,20 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>, s
237244
}
238245
var fn = workInProgress.type;
239246
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+
241255
if (typeof value === 'object' && value && typeof value.render === 'function') {
242256
// Proceed under the assumption that this is a class instance
243257
workInProgress.tag = ClassComponent;
244258
adoptClassInstance(workInProgress, value);
245259
mountClassInstance(workInProgress);
260+
ReactCurrentOwner.current = workInProgress;
246261
value = value.render();
247262
} else {
248263
// Proceed under the assumption that this is a functional component

src/renderers/shared/fiber/ReactFiberScheduler.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import type { PriorityLevel } from 'ReactPriorityLevel';
2020
var ReactFiberBeginWork = require('ReactFiberBeginWork');
2121
var ReactFiberCompleteWork = require('ReactFiberCompleteWork');
2222
var ReactFiberCommitWork = require('ReactFiberCommitWork');
23+
var ReactCurrentOwner = require('ReactCurrentOwner');
2324

2425
var { cloneFiber } = require('ReactFiber');
2526

@@ -306,6 +307,8 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
306307
}
307308
}
308309

310+
ReactCurrentOwner.current = null;
311+
309312
return next;
310313
}
311314

src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1072,4 +1072,28 @@ describe('ReactIncrementalSideEffects', () => {
10721072
// TODO: Test that mounts, updates, refs, unmounts and deletions happen in the
10731073
// expected way for aborted and resumed render life-cycles.
10741074

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+
});
10751099
});

0 commit comments

Comments
 (0)