UNPKG

15.6 kBJavaScriptView Raw
1/**
2 * Copyright (c) 2013-present, Facebook, Inc.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 *
8 */
9
10'use strict';
11
12var _require = require('./ReactChildFiber'),
13 reconcileChildFibers = _require.reconcileChildFibers,
14 reconcileChildFibersInPlace = _require.reconcileChildFibersInPlace,
15 cloneChildFibers = _require.cloneChildFibers;
16
17var _require2 = require('./ReactPriorityLevel'),
18 LowPriority = _require2.LowPriority;
19
20var ReactTypeOfWork = require('./ReactTypeOfWork');
21var IndeterminateComponent = ReactTypeOfWork.IndeterminateComponent,
22 FunctionalComponent = ReactTypeOfWork.FunctionalComponent,
23 ClassComponent = ReactTypeOfWork.ClassComponent,
24 HostContainer = ReactTypeOfWork.HostContainer,
25 HostComponent = ReactTypeOfWork.HostComponent,
26 CoroutineComponent = ReactTypeOfWork.CoroutineComponent,
27 CoroutineHandlerPhase = ReactTypeOfWork.CoroutineHandlerPhase,
28 YieldComponent = ReactTypeOfWork.YieldComponent;
29
30var _require3 = require('./ReactPriorityLevel'),
31 NoWork = _require3.NoWork,
32 OffscreenPriority = _require3.OffscreenPriority;
33
34var _require4 = require('./ReactFiberUpdateQueue'),
35 createUpdateQueue = _require4.createUpdateQueue,
36 addToQueue = _require4.addToQueue,
37 addCallbackToQueue = _require4.addCallbackToQueue,
38 mergeUpdateQueue = _require4.mergeUpdateQueue;
39
40var ReactInstanceMap = require('./ReactInstanceMap');
41
42module.exports = function (config, getScheduler) {
43 function markChildAsProgressed(current, workInProgress, priorityLevel) {
44 // We now have clones. Let's store them as the currently progressed work.
45 workInProgress.progressedChild = workInProgress.child;
46 workInProgress.progressedPriority = priorityLevel;
47 if (current) {
48 // We also store it on the current. When the alternate swaps in we can
49 // continue from this point.
50 current.progressedChild = workInProgress.progressedChild;
51 current.progressedPriority = workInProgress.progressedPriority;
52 }
53 }
54
55 function reconcileChildren(current, workInProgress, nextChildren) {
56 var priorityLevel = workInProgress.pendingWorkPriority;
57 reconcileChildrenAtPriority(current, workInProgress, nextChildren, priorityLevel);
58 }
59
60 function reconcileChildrenAtPriority(current, workInProgress, nextChildren, priorityLevel) {
61 // At this point any memoization is no longer valid since we'll have changed
62 // the children.
63 workInProgress.memoizedProps = null;
64 if (current && current.child === workInProgress.child) {
65 // If the current child is the same as the work in progress, it means that
66 // we haven't yet started any work on these children. Therefore, we use
67 // the clone algorithm to create a copy of all the current children.
68 workInProgress.child = reconcileChildFibers(workInProgress, workInProgress.child, nextChildren, priorityLevel);
69 } else {
70 // If, on the other hand, we don't have a current fiber or if it is
71 // already using a clone, that means we've already begun some work on this
72 // tree and we can continue where we left off by reconciling against the
73 // existing children.
74 workInProgress.child = reconcileChildFibersInPlace(workInProgress, workInProgress.child, nextChildren, priorityLevel);
75 }
76 markChildAsProgressed(current, workInProgress, priorityLevel);
77 }
78
79 function updateFunctionalComponent(current, workInProgress) {
80 var fn = workInProgress.type;
81 var props = workInProgress.pendingProps;
82
83 // TODO: Disable this before release, since it is not part of the public API
84 // I use this for testing to compare the relative overhead of classes.
85 if (typeof fn.shouldComponentUpdate === 'function') {
86 if (workInProgress.memoizedProps !== null) {
87 if (!fn.shouldComponentUpdate(workInProgress.memoizedProps, props)) {
88 return bailoutOnAlreadyFinishedWork(current, workInProgress);
89 }
90 }
91 }
92
93 var nextChildren = fn(props);
94 reconcileChildren(current, workInProgress, nextChildren);
95 return workInProgress.child;
96 }
97
98 function scheduleUpdate(fiber, updateQueue, priorityLevel) {
99 var _getScheduler = getScheduler(),
100 scheduleDeferredWork = _getScheduler.scheduleDeferredWork;
101
102 fiber.updateQueue = updateQueue;
103 // Schedule update on the alternate as well, since we don't know which tree
104 // is current.
105 if (fiber.alternate) {
106 fiber.alternate.updateQueue = updateQueue;
107 }
108 while (true) {
109 if (fiber.pendingWorkPriority === NoWork || fiber.pendingWorkPriority >= priorityLevel) {
110 fiber.pendingWorkPriority = priorityLevel;
111 }
112 if (fiber.alternate) {
113 if (fiber.alternate.pendingWorkPriority === NoWork || fiber.alternate.pendingWorkPriority >= priorityLevel) {
114 fiber.alternate.pendingWorkPriority = priorityLevel;
115 }
116 }
117 // Duck type root
118 if (fiber.stateNode && fiber.stateNode.containerInfo) {
119 var root = fiber.stateNode;
120 scheduleDeferredWork(root, priorityLevel);
121 return;
122 }
123 if (!fiber['return']) {
124 throw new Error('No root!');
125 }
126 fiber = fiber['return'];
127 }
128 }
129
130 // Class component state updater
131 var updater = {
132 enqueueSetState: function (instance, partialState) {
133 var fiber = ReactInstanceMap.get(instance);
134 var updateQueue = fiber.updateQueue ? addToQueue(fiber.updateQueue, partialState) : createUpdateQueue(partialState);
135 scheduleUpdate(fiber, updateQueue, LowPriority);
136 },
137 enqueueReplaceState: function (instance, state) {
138 var fiber = ReactInstanceMap.get(instance);
139 var updateQueue = createUpdateQueue(state);
140 updateQueue.isReplace = true;
141 scheduleUpdate(fiber, updateQueue, LowPriority);
142 },
143 enqueueForceUpdate: function (instance) {
144 var fiber = ReactInstanceMap.get(instance);
145 var updateQueue = fiber.updateQueue || createUpdateQueue(null);
146 updateQueue.isForced = true;
147 scheduleUpdate(fiber, updateQueue, LowPriority);
148 },
149 enqueueCallback: function (instance, callback) {
150 var fiber = ReactInstanceMap.get(instance);
151 var updateQueue = fiber.updateQueue ? fiber.updateQueue : createUpdateQueue(null);
152 addCallbackToQueue(updateQueue, callback);
153 fiber.updateQueue = updateQueue;
154 if (fiber.alternate) {
155 fiber.alternate.updateQueue = updateQueue;
156 }
157 }
158 };
159
160 function updateClassComponent(current, workInProgress) {
161 // A class component update is the result of either new props or new state.
162 // Account for the possibly of missing pending props by falling back to the
163 // memoized props.
164 var props = workInProgress.pendingProps;
165 if (!props && current) {
166 props = current.memoizedProps;
167 }
168 // Compute the state using the memoized state and the update queue.
169 var updateQueue = workInProgress.updateQueue;
170 var previousState = current ? current.memoizedState : null;
171 var state = updateQueue ? mergeUpdateQueue(updateQueue, previousState, props) : previousState;
172
173 var instance = workInProgress.stateNode;
174 if (!instance) {
175 var ctor = workInProgress.type;
176 workInProgress.stateNode = instance = new ctor(props);
177 state = instance.state || null;
178 // The initial state must be added to the update queue in case
179 // setState is called before the initial render.
180 if (state !== null) {
181 workInProgress.updateQueue = createUpdateQueue(state);
182 }
183 // The instance needs access to the fiber so that it can schedule updates
184 ReactInstanceMap.set(instance, workInProgress);
185 instance.updater = updater;
186 } else if (typeof instance.shouldComponentUpdate === 'function' && !(updateQueue && updateQueue.isForced)) {
187 if (workInProgress.memoizedProps !== null) {
188 // Reset the props, in case this is a ping-pong case rather than a
189 // completed update case. For the completed update case, the instance
190 // props will already be the memoizedProps.
191 instance.props = workInProgress.memoizedProps;
192 instance.state = workInProgress.memoizedState;
193 if (!instance.shouldComponentUpdate(props, state)) {
194 return bailoutOnAlreadyFinishedWork(current, workInProgress);
195 }
196 }
197 }
198
199 instance.props = props;
200 instance.state = state;
201 var nextChildren = instance.render();
202 reconcileChildren(current, workInProgress, nextChildren);
203
204 return workInProgress.child;
205 }
206
207 function updateHostComponent(current, workInProgress) {
208 var nextChildren = workInProgress.pendingProps.children;
209 if (workInProgress.pendingProps.hidden && workInProgress.pendingWorkPriority !== OffscreenPriority) {
210 // If this host component is hidden, we can bail out on the children.
211 // We'll rerender the children later at the lower priority.
212
213 // It is unfortunate that we have to do the reconciliation of these
214 // children already since that will add them to the tree even though
215 // they are not actually done yet. If this is a large set it is also
216 // confusing that this takes time to do right now instead of later.
217
218 if (workInProgress.progressedPriority === OffscreenPriority) {
219 // If we already made some progress on the offscreen priority before,
220 // then we should continue from where we left off.
221 workInProgress.child = workInProgress.progressedChild;
222 }
223
224 // Reconcile the children and stash them for later work.
225 reconcileChildrenAtPriority(current, workInProgress, nextChildren, OffscreenPriority);
226 workInProgress.child = current ? current.child : null;
227 // Abort and don't process children yet.
228 return null;
229 } else {
230 reconcileChildren(current, workInProgress, nextChildren);
231 return workInProgress.child;
232 }
233 }
234
235 function mountIndeterminateComponent(current, workInProgress) {
236 var fn = workInProgress.type;
237 var props = workInProgress.pendingProps;
238 var value = fn(props);
239 if (typeof value === 'object' && value && typeof value.render === 'function') {
240 // Proceed under the assumption that this is a class instance
241 workInProgress.tag = ClassComponent;
242 if (current) {
243 current.tag = ClassComponent;
244 }
245 value = value.render();
246 } else {
247 // Proceed under the assumption that this is a functional component
248 workInProgress.tag = FunctionalComponent;
249 if (current) {
250 current.tag = FunctionalComponent;
251 }
252 }
253 reconcileChildren(current, workInProgress, value);
254 return workInProgress.child;
255 }
256
257 function updateCoroutineComponent(current, workInProgress) {
258 var coroutine = workInProgress.pendingProps;
259 if (!coroutine) {
260 throw new Error('Should be resolved by now');
261 }
262 reconcileChildren(current, workInProgress, coroutine.children);
263 }
264
265 /*
266 function reuseChildrenEffects(returnFiber : Fiber, firstChild : Fiber) {
267 let child = firstChild;
268 do {
269 // Ensure that the first and last effect of the parent corresponds
270 // to the children's first and last effect.
271 if (!returnFiber.firstEffect) {
272 returnFiber.firstEffect = child.firstEffect;
273 }
274 if (child.lastEffect) {
275 if (returnFiber.lastEffect) {
276 returnFiber.lastEffect.nextEffect = child.firstEffect;
277 }
278 returnFiber.lastEffect = child.lastEffect;
279 }
280 } while (child = child.sibling);
281 }
282 */
283
284 function bailoutOnAlreadyFinishedWork(current, workInProgress) {
285 var priorityLevel = workInProgress.pendingWorkPriority;
286
287 // TODO: We should ideally be able to bail out early if the children have no
288 // more work to do. However, since we don't have a separation of this
289 // Fiber's priority and its children yet - we don't know without doing lots
290 // of the same work we do anyway. Once we have that separation we can just
291 // bail out here if the children has no more work at this priority level.
292 // if (workInProgress.priorityOfChildren <= priorityLevel) {
293 // // If there are side-effects in these children that have not yet been
294 // // committed we need to ensure that they get properly transferred up.
295 // if (current && current.child !== workInProgress.child) {
296 // reuseChildrenEffects(workInProgress, child);
297 // }
298 // return null;
299 // }
300
301 cloneChildFibers(current, workInProgress);
302 markChildAsProgressed(current, workInProgress, priorityLevel);
303 return workInProgress.child;
304 }
305
306 function bailoutOnLowPriority(current, workInProgress) {
307 if (current) {
308 workInProgress.child = current.child;
309 workInProgress.memoizedProps = current.memoizedProps;
310 workInProgress.output = current.output;
311 }
312 return null;
313 }
314
315 function beginWork(current, workInProgress, priorityLevel) {
316 if (workInProgress.pendingWorkPriority === NoWork || workInProgress.pendingWorkPriority > priorityLevel) {
317 return bailoutOnLowPriority(current, workInProgress);
318 }
319
320 if (workInProgress.progressedPriority === priorityLevel) {
321 // If we have progressed work on this priority level already, we can
322 // proceed this that as the child.
323 workInProgress.child = workInProgress.progressedChild;
324 }
325
326 if ((workInProgress.pendingProps === null || workInProgress.memoizedProps !== null && workInProgress.pendingProps === workInProgress.memoizedProps) && workInProgress.updateQueue === null) {
327 return bailoutOnAlreadyFinishedWork(current, workInProgress);
328 }
329
330 switch (workInProgress.tag) {
331 case IndeterminateComponent:
332 return mountIndeterminateComponent(current, workInProgress);
333 case FunctionalComponent:
334 return updateFunctionalComponent(current, workInProgress);
335 case ClassComponent:
336 return updateClassComponent(current, workInProgress);
337 case HostContainer:
338 reconcileChildren(current, workInProgress, workInProgress.pendingProps);
339 // A yield component is just a placeholder, we can just run through the
340 // next one immediately.
341 if (workInProgress.child) {
342 return beginWork(workInProgress.child.alternate, workInProgress.child, priorityLevel);
343 }
344 return null;
345 case HostComponent:
346 if (workInProgress.stateNode && typeof config.beginUpdate === 'function') {
347 config.beginUpdate(workInProgress.stateNode);
348 }
349 return updateHostComponent(current, workInProgress);
350 case CoroutineHandlerPhase:
351 // This is a restart. Reset the tag to the initial phase.
352 workInProgress.tag = CoroutineComponent;
353 // Intentionally fall through since this is now the same.
354 case CoroutineComponent:
355 updateCoroutineComponent(current, workInProgress);
356 // This doesn't take arbitrary time so we could synchronously just begin
357 // eagerly do the work of workInProgress.child as an optimization.
358 if (workInProgress.child) {
359 return beginWork(workInProgress.child.alternate, workInProgress.child, priorityLevel);
360 }
361 return workInProgress.child;
362 case YieldComponent:
363 // A yield component is just a placeholder, we can just run through the
364 // next one immediately.
365 if (workInProgress.sibling) {
366 return beginWork(workInProgress.sibling.alternate, workInProgress.sibling, priorityLevel);
367 }
368 return null;
369 default:
370 throw new Error('Unknown unit of work tag');
371 }
372 }
373
374 return {
375 beginWork: beginWork
376 };
377};
\No newline at end of file