@samitouri / QOS-React / commits / c83a0428f1

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