Skip to content

Commit aa14cc2

Browse files
committed
Implement string refs using callback closures
1 parent 51e1937 commit aa14cc2

File tree

6 files changed

+79
-10
lines changed

6 files changed

+79
-10
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/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: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ export type Fiber = Instance & {
8080

8181
// The ref last used to attach this node.
8282
// I'll avoid adding an owner field for prod and model that as functions.
83-
ref: null | (handle : ?Object) => void,
83+
ref: null | (((handle : ?Object) => void) & { _stringRef: ?string }),
8484

8585
// Input is the data coming into process this fiber. Arguments. Props.
8686
pendingProps: any, // This type will be more specific once we overload the tag.
@@ -327,4 +327,3 @@ exports.createFiberFromYield = function(yieldNode : ReactYield, priorityLevel :
327327
fiber.pendingProps = {};
328328
return fiber;
329329
};
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

@@ -298,6 +299,8 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
298299
}
299300
}
300301

302+
ReactCurrentOwner.current = null;
303+
301304
return next;
302305
}
303306

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)