Minimize abuse of .alternate
This avoids using .alternate as much as possible and isolates it to the root. For anything that is "work in progress" this happens to be the same as the alternate field. To avoid an extra "current" field on work in progress fibers, we can just use the alternate. The timing for when this is true might be a bit tricky to reason about so I explicitly pass the current value everywhere from the top. That way we can always change this in the future to use an explicit field or we can try to maintain a parallel data structure that remembers which was the "current" fiber for each wip child.
Sebastian Markbage committed
Jun 7, 2016 at 15:02 UTC
c83a0428f126e9f41d199b56fe8bf457dce5dad2
4 files changed
+79
-64
src/renderers/shared/fiber/ReactChildFiber.js
+23
-20
@@ -28,7 +28,7 @@ var {
28
var ReactFiber = require('ReactFiber');
29
var ReactReifiedYield = require('ReactReifiedYield');
30
31
-function createSubsequentChild(parent : Fiber, nextReusable : ?Fiber, previousSibling : Fiber, newChildren) : Fiber {
31
+function createSubsequentChild(parent : Fiber, existingChild : ?Fiber, previousSibling : Fiber, newChildren) : Fiber {
32
if (typeof newChildren !== 'object' || newChildren === null) {
33
return previousSibling;
34
}
@@ -36,14 +36,14 @@ function createSubsequentChild(parent : Fiber, nextReusable : ?Fiber, previousSi
36
switch (newChildren.$$typeof) {
37
case REACT_ELEMENT_TYPE: {
38
const element = (newChildren : ReactElement<any>);
39
- if (nextReusable &&
40
- element.type === nextReusable.type &&
41
- element.key === nextReusable.key) {
39
+ if (existingChild &&
40
+ element.type === existingChild.type &&
41
+ element.key === existingChild.key) {
42
// TODO: This is not sufficient since previous siblings could be new.
43
// Will fix reconciliation properly later.
44
- const clone = ReactFiber.cloneFiber(nextReusable);
44
+ const clone = ReactFiber.cloneFiber(existingChild);
45
clone.input = element.props;
46
- clone.child = nextReusable.child;
46
+ clone.child = existingChild.child;
47
clone.sibling = null;
48
previousSibling.sibling = clone;
49
return clone;
@@ -75,12 +75,14 @@ function createSubsequentChild(parent : Fiber, nextReusable : ?Fiber, previousSi
75
76
if (Array.isArray(newChildren)) {
77
let prev : Fiber = previousSibling;
78
+ let existing : ?Fiber = existingChild;
79
for (var i = 0; i < newChildren.length; i++) {
79
- let reusable = null;
80
- if (prev.alternate) {
81
- reusable = prev.alternate.sibling;
80
+ prev = createSubsequentChild(parent, existing, prev, newChildren[i]);
81
+ if (prev && existing) {
82
+ // TODO: This is not correct because there could've been more
83
+ // than one sibling consumed but I don't want to return a tuple.
84
+ existing = existing.sibling;
85
}
83
- prev = createSubsequentChild(parent, reusable, prev, newChildren[i]);
86
}
87
return prev;
88
} else {
@@ -89,7 +91,7 @@ function createSubsequentChild(parent : Fiber, nextReusable : ?Fiber, previousSi
91
}
92
}
93
92
-function createFirstChild(parent, newChildren) {
94
+function createFirstChild(parent, existingChild, newChildren) {
95
if (typeof newChildren !== 'object' || newChildren === null) {
96
return null;
97
}
@@ -97,7 +99,6 @@ function createFirstChild(parent, newChildren) {
99
switch (newChildren.$$typeof) {
100
case REACT_ELEMENT_TYPE: {
101
const element = (newChildren : ReactElement<any>);
100
- const existingChild : ?Fiber = parent.child;
102
if (existingChild &&
103
element.type === existingChild.type &&
104
element.key === existingChild.key) {
@@ -136,16 +137,18 @@ function createFirstChild(parent, newChildren) {
137
if (Array.isArray(newChildren)) {
138
var first : ?Fiber = null;
139
var prev : ?Fiber = null;
140
+ var existing : ?Fiber = existingChild;
141
for (var i = 0; i < newChildren.length; i++) {
142
if (prev == null) {
141
- prev = createFirstChild(parent, newChildren[i]);
143
+ prev = createFirstChild(parent, existing, newChildren[i]);
144
first = prev;
145
} else {
144
- let reusable = null;
145
- if (prev.alternate) {
146
- reusable = prev.alternate.sibling;
147
- }
148
- prev = createSubsequentChild(parent, reusable, prev, newChildren[i]);
146
+ prev = createSubsequentChild(parent, existing, prev, newChildren[i]);
147
+ }
148
+ if (prev && existing) {
149
+ // TODO: This is not correct because there could've been more
150
+ // than one sibling consumed but I don't want to return a tuple.
151
+ existing = existing.sibling;
152
}
153
}
154
return first;
@@ -155,6 +158,6 @@ function createFirstChild(parent, newChildren) {
158
}
159
}
160
158
-exports.reconcileChildFibers = function(parent : Fiber, firstChild : ?Fiber, newChildren : ReactNodeList) : ?Fiber {
159
- return createFirstChild(parent, newChildren);
161
+exports.reconcileChildFibers = function(parent : Fiber, currentFirstChild : ?Fiber, newChildren : ReactNodeList) : ?Fiber {
162
+ return createFirstChild(parent, currentFirstChild, newChildren);
163
};
src/renderers/shared/fiber/ReactFiberBeginWork.js
+37
-36
@@ -27,31 +27,30 @@ var {
27
YieldComponent,
28
} = ReactTypesOfWork;
29
30
-function updateFunctionalComponent(workInProgress) {
31
- var fn = workInProgress.type;
32
- var props = workInProgress.input;
33
- console.log('update fn:', fn.name);
34
- var nextChildren = fn(props);
35
-
30
+function reconcileChildren(current, workInProgress, nextChildren) {
31
workInProgress.child = ReactChildFiber.reconcileChildFibers(
32
workInProgress,
38
- workInProgress.child,
33
+ current ? current.child : null,
34
nextChildren
35
);
36
}
37
43
-function updateHostComponent(workInProgress) {
38
+function updateFunctionalComponent(current, workInProgress) {
39
+ var fn = workInProgress.type;
40
+ var props = workInProgress.input;
41
+ console.log('update fn:', fn.name);
42
+ var nextChildren = fn(props);
43
+ reconcileChildren(current, workInProgress, nextChildren);
44
+}
45
+
46
+function updateHostComponent(current, workInProgress) {
47
console.log('host component', workInProgress.type, typeof workInProgress.input.children === 'string' ? workInProgress.input.children : '');
48
49
var nextChildren = workInProgress.input.children;
47
- workInProgress.child = ReactChildFiber.reconcileChildFibers(
48
- workInProgress,
49
- workInProgress.child,
50
- nextChildren
51
- );
50
+ reconcileChildren(current, workInProgress, nextChildren);
51
}
52
54
-function mountIndeterminateComponent(workInProgress) {
53
+function mountIndeterminateComponent(current, workInProgress) {
54
var fn = workInProgress.type;
55
var props = workInProgress.input;
56
var value = fn(props);
@@ -64,34 +63,30 @@ function mountIndeterminateComponent(workInProgress) {
63
// Proceed under the assumption that this is a functional component
64
workInProgress.tag = FunctionalComponent;
65
}
67
- workInProgress.child = ReactChildFiber.reconcileChildFibers(
68
- workInProgress,
69
- workInProgress.child,
70
- value
71
- );
66
+ reconcileChildren(current, workInProgress, value);
67
}
68
74
-function updateCoroutineComponent(workInProgress) {
69
+function updateCoroutineComponent(current, workInProgress) {
70
var coroutine = (workInProgress.input : ?ReactCoroutine);
71
if (!coroutine) {
72
throw new Error('Should be resolved by now');
73
}
74
console.log('begin coroutine', workInProgress.type.name);
80
- workInProgress.child = ReactChildFiber.reconcileChildFibers(
81
- workInProgress,
82
- workInProgress.child,
83
- coroutine.children
84
- );
75
+ reconcileChildren(current, workInProgress, coroutine.children);
76
}
77
87
-function beginWork(workInProgress : Fiber) : ?Fiber {
88
- const alt = workInProgress.alternate;
89
- if (alt && workInProgress.input === alt.memoizedInput) {
78
+function beginWork(current : ?Fiber, workInProgress : Fiber) : ?Fiber {
79
+ // The current, flushed, state of this fiber is the alternate.
80
+ // Ideally nothing should rely on this, but relying on it here
81
+ // means that we don't need an additional field on the work in
82
+ // progress.
83
+ if (current && workInProgress.input === current.memoizedInput) {
84
// The most likely scenario is that the previous copy of the tree contains
85
// the same input as the new one. In that case, we can just copy the output
86
// and children from that node.
93
- workInProgress.output = alt.output;
94
- workInProgress.child = alt.child;
87
+ workInProgress.output = current.output;
88
+ workInProgress.child = current.child;
89
+ workInProgress.stateNode = current.stateNode;
90
return null;
91
}
92
if (workInProgress.input === workInProgress.memoizedInput) {
@@ -101,34 +96,40 @@ function beginWork(workInProgress : Fiber) : ?Fiber {
96
}
97
switch (workInProgress.tag) {
98
case IndeterminateComponent:
104
- mountIndeterminateComponent(workInProgress);
99
+ mountIndeterminateComponent(current, workInProgress);
100
break;
101
case FunctionalComponent:
107
- updateFunctionalComponent(workInProgress);
102
+ updateFunctionalComponent(current, workInProgress);
103
break;
104
case ClassComponent:
105
console.log('class component', workInProgress.input.type.name);
106
break;
107
case HostComponent:
113
- updateHostComponent(workInProgress);
108
+ updateHostComponent(current, workInProgress);
109
break;
110
case CoroutineHandlerPhase:
111
// This is a restart. Reset the tag to the initial phase.
112
workInProgress.tag = CoroutineComponent;
113
// Intentionally fall through since this is now the same.
114
case CoroutineComponent:
120
- updateCoroutineComponent(workInProgress);
115
+ updateCoroutineComponent(current, workInProgress);
116
// This doesn't take arbitrary time so we could synchronously just begin
117
// eagerly do the work of workInProgress.child as an optimization.
118
if (workInProgress.child) {
124
- return beginWork(workInProgress.child);
119
+ return beginWork(
120
+ workInProgress.child.alternate,
121
+ workInProgress.child
122
+ );
123
}
124
break;
125
case YieldComponent:
126
// A yield component is just a placeholder, we can just run through the
127
// next one immediately.
128
if (workInProgress.sibling) {
131
- return beginWork(workInProgress.sibling);
129
+ return beginWork(
130
+ workInProgress.sibling.alternate,
131
+ workInProgress.sibling
132
+ );
133
}
134
return null;
135
default:
src/renderers/shared/fiber/ReactFiberCompleteWork.js
+5
-4
@@ -54,7 +54,7 @@ function recursivelyFillYields(yields, output : ?Fiber | ?ReifiedYield) {
54
}
55
}
56
57
-function moveCoroutineToHandlerPhase(workInProgress : Fiber) {
57
+function moveCoroutineToHandlerPhase(current : ?Fiber, workInProgress : Fiber) {
58
var coroutine = (workInProgress.input : ?ReactCoroutine);
59
if (!coroutine) {
60
throw new Error('Should be resolved by now');
@@ -81,15 +81,16 @@ function moveCoroutineToHandlerPhase(workInProgress : Fiber) {
81
var props = coroutine.props;
82
var nextChildren = fn(props, yields);
83
84
+ var currentFirstChild = current ? current.stateNode : null;
85
workInProgress.stateNode = ReactChildFiber.reconcileChildFibers(
86
workInProgress,
86
- workInProgress.stateNode,
87
+ currentFirstChild,
88
nextChildren
89
);
90
return workInProgress.stateNode;
91
}
92
92
-exports.completeWork = function(workInProgress : Fiber) : ?Fiber {
93
+exports.completeWork = function(current : ?Fiber, workInProgress : Fiber) : ?Fiber {
94
switch (workInProgress.tag) {
95
case FunctionalComponent:
96
console.log('/functional component', workInProgress.type.name);
@@ -104,7 +105,7 @@ exports.completeWork = function(workInProgress : Fiber) : ?Fiber {
105
break;
106
case CoroutineComponent:
107
console.log('/coroutine component', workInProgress.input.handler.name);
107
- return moveCoroutineToHandlerPhase(workInProgress);
108
+ return moveCoroutineToHandlerPhase(current, workInProgress);
109
case CoroutineHandlerPhase:
110
transferOutput(workInProgress.stateNode, workInProgress);
111
// Reset the tag to now be a first phase coroutine.
src/renderers/shared/fiber/ReactFiberReconciler.js
+14
-4
@@ -51,7 +51,12 @@ module.exports = function<T, P, I>(config : HostConfig<T, P, I>) : Reconciler {
51
52
function completeUnitOfWork(workInProgress : Fiber) : ?Fiber {
53
while (true) {
54
- var next = completeWork(workInProgress);
54
+ // The current, flushed, state of this fiber is the alternate.
55
+ // Ideally nothing should rely on this, but relying on it here
56
+ // means that we don't need an additional field on the work in
57
+ // progress.
58
+ const current = workInProgress.alternate;
59
+ const next = completeWork(current, workInProgress);
60
if (next) {
61
// If completing this work spawned new work, do that next.
62
return next;
@@ -74,7 +79,12 @@ module.exports = function<T, P, I>(config : HostConfig<T, P, I>) : Reconciler {
79
}
80
81
function performUnitOfWork(workInProgress : Fiber) : ?Fiber {
77
- var next = beginWork(workInProgress);
82
+ // The current, flushed, state of this fiber is the alternate.
83
+ // Ideally nothing should rely on this, but relying on it here
84
+ // means that we don't need an additional field on the work in
85
+ // progress.
86
+ const current = workInProgress.alternate;
87
+ const next = beginWork(current, workInProgress);
88
if (next) {
89
// If this spawns new work, do that next.
90
return next;
@@ -123,8 +133,8 @@ module.exports = function<T, P, I>(config : HostConfig<T, P, I>) : Reconciler {
133
// TODO: Unify this with ReactChildFiber. We can't now because the parent
134
// is passed. Should be doable though. Might require a wrapper don't know.
135
if (rootFiber && rootFiber.type === element.type && rootFiber.key === element.key) {
126
- nextUnitOfWork = rootFiber;
127
- rootFiber.input = element.props;
136
+ nextUnitOfWork = ReactFiber.cloneFiber(rootFiber);
137
+ nextUnitOfWork.input = element.props;
138
return {};
139
}
140