Create HostContainer Component Type
This is essentially equivalent to the current top level wrappers. They contain the next children to be mounted into a container node from the host. It is the responsibility of the host to retain references to them for updates. I expect them to be able to exist in the middle of the tree in the future, for renderSubtreeIntoContainer.
Sebastian Markbage committed
Jun 13, 2016 at 19:43 UTC
f04c38ed656a513fe5515e54d7645883767a177e
7 files changed
+161
-27
src/renderers/noop/ReactNoop.js
+7
-3
@@ -38,12 +38,16 @@ var NoopRenderer = ReactFiberReconciler({
38
39
});
40
41
+var root = null;
42
+
43
var ReactNoop = {
44
45
render(element : ReactElement<any>) {
44
-
45
- NoopRenderer.mountNewRoot(element);
46
-
46
+ if (!root) {
47
+ root = NoopRenderer.mountContainer(element, null);
48
+ } else {
49
+ NoopRenderer.updateContainer(element, root);
50
+ }
51
},
52
53
flushHighPri() {
src/renderers/shared/fiber/ReactChildFiber.js
+2
@@ -158,6 +158,8 @@ function createFirstChild(parent, existingChild, newChildren) {
158
}
159
}
160
161
+// TODO: This API won't work because we'll need to transfer the side-effects of
162
+// unmounting children to the parent.
163
exports.reconcileChildFibers = function(parent : Fiber, currentFirstChild : ?Fiber, newChildren : ReactNodeList) : ?Fiber {
164
return createFirstChild(parent, currentFirstChild, newChildren);
165
};
src/renderers/shared/fiber/ReactFiber.js
+7
@@ -19,6 +19,7 @@ var ReactTypeOfWork = require('ReactTypeOfWork');
19
var {
20
IndeterminateComponent,
21
ClassComponent,
22
+ HostContainer,
23
HostComponent,
24
CoroutineComponent,
25
YieldComponent,
@@ -147,6 +148,12 @@ exports.cloneFiber = function(fiber : Fiber) : Fiber {
148
return alt;
149
};
150
151
+exports.createHostContainerFiber = function(containerInfo : ?Object) {
152
+ const fiber = createFiber(HostContainer, null);
153
+ fiber.stateNode = containerInfo;
154
+ return fiber;
155
+};
156
+
157
exports.createFiberFromElement = function(element : ReactElement) {
158
const fiber = exports.createFiberFromElementType(element.type, element.key);
159
fiber.pendingProps = element.props;
src/renderers/shared/fiber/ReactFiberBeginWork.js
+12
@@ -21,6 +21,7 @@ var {
21
IndeterminateComponent,
22
FunctionalComponent,
23
ClassComponent,
24
+ HostContainer,
25
HostComponent,
26
CoroutineComponent,
27
CoroutineHandlerPhase,
@@ -104,6 +105,17 @@ function beginWork(current : ?Fiber, workInProgress : Fiber) : ?Fiber {
105
case ClassComponent:
106
console.log('class component', workInProgress.pendingProps.type.name);
107
return workInProgress.child;
108
+ case HostContainer:
109
+ reconcileChildren(current, workInProgress, workInProgress.pendingProps);
110
+ // A yield component is just a placeholder, we can just run through the
111
+ // next one immediately.
112
+ if (workInProgress.child) {
113
+ return beginWork(
114
+ workInProgress.child.alternate,
115
+ workInProgress.child
116
+ );
117
+ }
118
+ return null;
119
case HostComponent:
120
updateHostComponent(current, workInProgress);
121
return workInProgress.child;
src/renderers/shared/fiber/ReactFiberCompleteWork.js
+3
@@ -23,6 +23,7 @@ var {
23
IndeterminateComponent,
24
FunctionalComponent,
25
ClassComponent,
26
+ HostContainer,
27
HostComponent,
28
CoroutineComponent,
29
CoroutineHandlerPhase,
@@ -100,6 +101,8 @@ exports.completeWork = function(current : ?Fiber, workInProgress : Fiber) : ?Fib
101
console.log('/class component', workInProgress.type.name);
102
transferOutput(workInProgress.child, workInProgress);
103
return null;
104
+ case HostContainer:
105
+ return null;
106
case HostComponent:
107
console.log('/host component', workInProgress.type);
108
return null;
src/renderers/shared/fiber/ReactFiberReconciler.js
+124
-19
@@ -13,10 +13,19 @@
13
'use strict';
14
15
import type { Fiber } from 'ReactFiber';
16
+import type { PriorityLevel } from 'ReactPriorityLevel';
17
+
18
var ReactFiber = require('ReactFiber');
19
var { beginWork } = require('ReactFiberBeginWork');
20
var { completeWork } = require('ReactFiberCompleteWork');
21
22
+var {
23
+ NoWork,
24
+ HighPriority,
25
+ LowPriority,
26
+ OffscreenPriority,
27
+} = require('ReactPriorityLevel');
28
+
29
type ReactHostElement<T, P> = {
30
type: T,
31
props: P
@@ -36,10 +45,15 @@ export type HostConfig<T, P, I> = {
45
46
};
47
39
-type OpaqueID = {};
48
+type OpaqueNode = Fiber;
49
50
export type Reconciler = {
42
- mountNewRoot(element : ReactElement<any>) : OpaqueID;
51
+ mountContainer(element : ReactElement<any>, containerInfo : ?Object) : OpaqueNode,
52
+ updateContainer(element : ReactElement<any>, container : OpaqueNode) : void,
53
+ unmountContainer(container : OpaqueNode) : void,
54
+
55
+ // Used to extract the return value from the initial render. Legacy API.
56
+ getPublicRootInstance(container : OpaqueNode) : ?Object,
57
};
58
59
module.exports = function<T, P, I>(config : HostConfig<T, P, I>) : Reconciler {
@@ -49,6 +63,49 @@ module.exports = function<T, P, I>(config : HostConfig<T, P, I>) : Reconciler {
63
64
let nextUnitOfWork : ?Fiber = null;
65
66
+ let currentRootsWithPendingWork : ?Fiber = null;
67
+
68
+ function findNextUnitOfWork(priorityLevel : PriorityLevel) : ?Fiber {
69
+ let current = currentRootsWithPendingWork;
70
+ while (current) {
71
+ if (current.pendingWorkPriority !== 0 &&
72
+ current.pendingWorkPriority <= priorityLevel) {
73
+ // This node has work to do that fits our priority level criteria.
74
+ if (current.pendingProps !== null) {
75
+ // We found some work to do. We need to return the "work in progress"
76
+ // of this node which will be the alternate.
77
+ return current.alternate;
78
+ }
79
+ // If we have a child let's see if any of our children has work to do.
80
+ // Only bother doing this at all if the current priority level matches
81
+ // because it is the highest priority for the whole subtree.
82
+ if (current.child) {
83
+ current = current.child;
84
+ continue;
85
+ }
86
+ // If we match the priority but has no child and no work to do,
87
+ // then we can safely reset the flag.
88
+ current.pendingWorkPriority = NoWork;
89
+ }
90
+ while (!current.sibling) {
91
+ // TODO: Stop using parent here. See below.
92
+ // $FlowFixMe: This downcast is not safe. It is intentionally an error.
93
+ current = current.parent;
94
+ if (!current) {
95
+ return null;
96
+ }
97
+ if (current.pendingWorkPriority <= priorityLevel) {
98
+ // If this subtree had work left to do, we would have returned it by
99
+ // now. This could happen if a child with pending work gets cleaned up
100
+ // but we don't clear the flag then. It is safe to reset it now.
101
+ current.pendingWorkPriority = NoWork;
102
+ }
103
+ }
104
+ current = current.sibling;
105
+ }
106
+ return null;
107
+ }
108
+
109
function completeUnitOfWork(workInProgress : Fiber) : ?Fiber {
110
while (true) {
111
// The current, flushed, state of this fiber is the alternate.
@@ -73,6 +130,7 @@ module.exports = function<T, P, I>(config : HostConfig<T, P, I>) : Reconciler {
130
workInProgress = workInProgress.parent;
131
} else {
132
// If we're at the root, there's no more work to do.
133
+ currentRootsWithPendingWork = null;
134
return null;
135
}
136
}
@@ -95,6 +153,19 @@ module.exports = function<T, P, I>(config : HostConfig<T, P, I>) : Reconciler {
153
}
154
155
function performLowPriWork(deadline : Deadline) {
156
+ if (!nextUnitOfWork) {
157
+ // Find the highest possible priority work to do.
158
+ // This loop is unrolled just to satisfy Flow's enum constraint.
159
+ // We could make arbitrary many idle priority levels but having
160
+ // too many just means flushing changes too often.
161
+ nextUnitOfWork = findNextUnitOfWork(HighPriority);
162
+ if (!nextUnitOfWork) {
163
+ nextUnitOfWork = findNextUnitOfWork(LowPriority);
164
+ if (!nextUnitOfWork) {
165
+ nextUnitOfWork = findNextUnitOfWork(OffscreenPriority);
166
+ }
167
+ }
168
+ }
169
while (nextUnitOfWork) {
170
if (deadline.timeRemaining() > timeHeuristicForUnitOfWork) {
171
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
@@ -105,11 +176,34 @@ module.exports = function<T, P, I>(config : HostConfig<T, P, I>) : Reconciler {
176
}
177
}
178
108
- function ensureLowPriIsScheduled() {
109
- if (nextUnitOfWork) {
110
- return;
179
+ function scheduleLowPriWork(root : Fiber) {
180
+ // We must reset the current unit of work pointer so that we restart the
181
+ // search from the root during the next tick.
182
+ nextUnitOfWork = null;
183
+
184
+ if (currentRootsWithPendingWork) {
185
+ if (root === currentRootsWithPendingWork) {
186
+ // We're already scheduled.
187
+ return;
188
+ }
189
+ // We already have some work pending in another root. Add this to the
190
+ // linked list.
191
+ let previousSibling = currentRootsWithPendingWork;
192
+ while (previousSibling.sibling) {
193
+ previousSibling = previousSibling.sibling;
194
+ if (root === previousSibling) {
195
+ // We're already scheduled.
196
+ return;
197
+ }
198
+ }
199
+ previousSibling.sibling = root;
200
+ } else {
201
+ // We're the first and only root with new changes.
202
+ currentRootsWithPendingWork = root;
203
+ root.sibling = null;
204
+ // Ensure we will get another callback to process the work.
205
+ scheduleLowPriCallback(performLowPriWork);
206
}
112
- scheduleLowPriCallback(performLowPriWork);
207
}
208
209
/*
@@ -122,26 +216,37 @@ module.exports = function<T, P, I>(config : HostConfig<T, P, I>) : Reconciler {
216
}
217
*/
218
125
- let rootFiber : ?Fiber = null;
126
-
219
return {
220
129
- mountNewRoot(element : ReactElement<any>) : OpaqueID {
221
+ mountContainer(element : ReactElement<any>, containerInfo : ?Object) : OpaqueNode {
222
+ const container = ReactFiber.createHostContainerFiber(containerInfo);
223
+ container.alternate = container;
224
+ container.pendingProps = element;
225
+ container.pendingWorkPriority = LowPriority;
226
131
- ensureLowPriIsScheduled();
227
+ scheduleLowPriWork(container);
228
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) {
136
- nextUnitOfWork = ReactFiber.cloneFiber(rootFiber);
137
- nextUnitOfWork.pendingProps = element.props;
138
- return {};
139
- }
229
+ return container;
230
+ },
231
141
- nextUnitOfWork = rootFiber = ReactFiber.createFiberFromElement(element);
232
+ updateContainer(element : ReactElement<any>, container : OpaqueNode) : void {
233
+ container.pendingProps = element;
234
+ container.pendingWorkPriority = LowPriority;
235
143
- return {};
236
+ scheduleLowPriWork(container);
237
+ },
238
+
239
+ unmountContainer(container : OpaqueNode) : void {
240
+ container.pendingProps = null;
241
+ container.pendingWorkPriority = LowPriority;
242
+
243
+ scheduleLowPriWork(container);
244
+ },
245
+
246
+ getPublicRootInstance(container : OpaqueNode) : ?Object {
247
+ return null;
248
},
249
250
};
251
+
252
};
src/renderers/shared/fiber/ReactTypeOfWork.js
+6
-5
@@ -12,14 +12,15 @@
12
13
'use strict';
14
15
-export type TypeOfWork = 0 | 1 | 2 | 3 | 4 | 5 | 6;
15
+export type TypeOfWork = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;
16
17
module.exports = {
18
IndeterminateComponent: 0, // Before we know whether it is functional or class
19
FunctionalComponent: 1,
20
ClassComponent: 2,
21
- HostComponent: 3,
22
- CoroutineComponent: 4,
23
- CoroutineHandlerPhase: 5,
24
- YieldComponent: 6,
21
+ HostContainer: 3, // Root of a host tree. Could be nested inside another node.
22
+ HostComponent: 4,
23
+ CoroutineComponent: 5,
24
+ CoroutineHandlerPhase: 6,
25
+ YieldComponent: 7,
26
};