@samitouri / QOS-React / commits / f04c38ed65

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 };