UNPKG

8.48 kBJavaScriptView Raw
1/**
2 * Copyright 2013-present, Facebook, Inc.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree. An additional grant
7 * of patent rights can be found in the PATENTS file in the same directory.
8 *
9 *
10 */
11
12'use strict';
13
14var _require = require('./ReactChildFiber'),
15 reconcileChildFibers = _require.reconcileChildFibers;
16
17var ReactTypeOfWork = require('./ReactTypeOfWork');
18var IndeterminateComponent = ReactTypeOfWork.IndeterminateComponent,
19 FunctionalComponent = ReactTypeOfWork.FunctionalComponent,
20 ClassComponent = ReactTypeOfWork.ClassComponent,
21 HostContainer = ReactTypeOfWork.HostContainer,
22 HostComponent = ReactTypeOfWork.HostComponent,
23 CoroutineComponent = ReactTypeOfWork.CoroutineComponent,
24 CoroutineHandlerPhase = ReactTypeOfWork.CoroutineHandlerPhase,
25 YieldComponent = ReactTypeOfWork.YieldComponent;
26
27
28module.exports = function (config) {
29 var createInstance = config.createInstance;
30 var prepareUpdate = config.prepareUpdate;
31
32 function markForPreEffect(workInProgress) {
33 // Schedule a side-effect on this fiber, BEFORE the children's side-effects.
34 if (workInProgress.firstEffect) {
35 workInProgress.nextEffect = workInProgress.firstEffect;
36 workInProgress.firstEffect = workInProgress;
37 } else {
38 workInProgress.firstEffect = workInProgress;
39 workInProgress.lastEffect = workInProgress;
40 }
41 }
42
43 // TODO: It's possible this will create layout thrash issues because mutations
44 // of the DOM and life-cycles are interleaved. E.g. if a componentDidMount
45 // of a sibling reads, then the next sibling updates and reads etc.
46 function markForPostEffect(workInProgress) {
47 // Schedule a side-effect on this fiber, AFTER the children's side-effects.
48 if (workInProgress.lastEffect) {
49 workInProgress.lastEffect.nextEffect = workInProgress;
50 } else {
51 workInProgress.firstEffect = workInProgress;
52 }
53 workInProgress.lastEffect = workInProgress;
54 }
55
56 function transferOutput(child, returnFiber) {
57 // If we have a single result, we just pass that through as the output to
58 // avoid unnecessary traversal. When we have multiple output, we just pass
59 // the linked list of fibers that has the individual output values.
60 returnFiber.output = child && !child.sibling ? child.output : child;
61 returnFiber.memoizedProps = returnFiber.pendingProps;
62 }
63
64 function recursivelyFillYields(yields, output) {
65 if (!output) {
66 // Ignore nulls etc.
67 } else if (output.tag !== undefined) {
68 // TODO: Fix this fragile duck test.
69 // Detect if this is a fiber, if so it is a fragment result.
70 // $FlowFixMe: Refinement issue.
71 var item = output;
72 do {
73 recursivelyFillYields(yields, item.output);
74 item = item.sibling;
75 } while (item);
76 } else {
77 // $FlowFixMe: Refinement issue. If it is not a Fiber or null, it is a yield
78 yields.push(output);
79 }
80 }
81
82 function moveCoroutineToHandlerPhase(current, workInProgress) {
83 var coroutine = workInProgress.pendingProps;
84 if (!coroutine) {
85 throw new Error('Should be resolved by now');
86 }
87
88 // First step of the coroutine has completed. Now we need to do the second.
89 // TODO: It would be nice to have a multi stage coroutine represented by a
90 // single component, or at least tail call optimize nested ones. Currently
91 // that requires additional fields that we don't want to add to the fiber.
92 // So this requires nested handlers.
93 // Note: This doesn't mutate the alternate node. I don't think it needs to
94 // since this stage is reset for every pass.
95 workInProgress.tag = CoroutineHandlerPhase;
96
97 // Build up the yields.
98 // TODO: Compare this to a generator or opaque helpers like Children.
99 var yields = [];
100 var child = workInProgress.child;
101 while (child) {
102 recursivelyFillYields(yields, child.output);
103 child = child.sibling;
104 }
105 var fn = coroutine.handler;
106 var props = coroutine.props;
107 var nextChildren = fn(props, yields);
108
109 var currentFirstChild = current ? current.stateNode : null;
110 // Inherit the priority of the returnFiber.
111 var priority = workInProgress.pendingWorkPriority;
112 workInProgress.stateNode = reconcileChildFibers(workInProgress, currentFirstChild, nextChildren, priority);
113 return workInProgress.stateNode;
114 }
115
116 function completeWork(current, workInProgress) {
117 switch (workInProgress.tag) {
118 case FunctionalComponent:
119 transferOutput(workInProgress.child, workInProgress);
120 return null;
121 case ClassComponent:
122 transferOutput(workInProgress.child, workInProgress);
123 // Don't use the state queue to compute the memoized state. We already
124 // merged it and assigned it to the instance. Transfer it from there.
125 // Also need to transfer the props, because pendingProps will be null
126 // in the case of an update
127 var _workInProgress$state = workInProgress.stateNode,
128 state = _workInProgress$state.state,
129 props = _workInProgress$state.props;
130
131 workInProgress.memoizedState = state;
132 workInProgress.memoizedProps = props;
133 // Transfer update queue to callbackList field so callbacks can be
134 // called during commit phase.
135 workInProgress.callbackList = workInProgress.updateQueue;
136 markForPostEffect(workInProgress);
137 return null;
138 case HostContainer:
139 transferOutput(workInProgress.child, workInProgress);
140 // We don't know if a container has updated any children so we always
141 // need to update it right now. We schedule this side-effect before
142 // all the other side-effects in the subtree. We need to schedule it
143 // before so that the entire tree is up-to-date before the life-cycles
144 // are invoked.
145 markForPreEffect(workInProgress);
146 return null;
147 case HostComponent:
148 var newProps = workInProgress.pendingProps;
149 var child = workInProgress.child;
150 var children = child && !child.sibling ? child.output : child;
151 if (current && workInProgress.stateNode != null) {
152 // If we have an alternate, that means this is an update and we need to
153 // schedule a side-effect to do the updates.
154 var oldProps = current.memoizedProps;
155 // If we get updated because one of our children updated, we don't
156 // have newProps so we'll have to reuse them.
157 // TODO: Split the update API as separate for the props vs. children.
158 // Even better would be if children weren't special cased at all tho.
159 if (!newProps) {
160 newProps = oldProps;
161 }
162 var instance = workInProgress.stateNode;
163 if (prepareUpdate(instance, oldProps, newProps, children)) {
164 // This returns true if there was something to update.
165 markForPreEffect(workInProgress);
166 }
167 // TODO: Is this actually ever going to change? Why set it every time?
168 workInProgress.output = instance;
169 } else {
170 if (!newProps) {
171 if (workInProgress.stateNode === null) {
172 throw new Error('We must have new props for new mounts.');
173 } else {
174 // This can happen when we abort work.
175 return null;
176 }
177 }
178 var _instance = createInstance(workInProgress.type, newProps, children);
179 // TODO: This seems like unnecessary duplication.
180 workInProgress.stateNode = _instance;
181 workInProgress.output = _instance;
182 }
183 workInProgress.memoizedProps = newProps;
184 return null;
185 case CoroutineComponent:
186 return moveCoroutineToHandlerPhase(current, workInProgress);
187 case CoroutineHandlerPhase:
188 transferOutput(workInProgress.stateNode, workInProgress);
189 // Reset the tag to now be a first phase coroutine.
190 workInProgress.tag = CoroutineComponent;
191 return null;
192 case YieldComponent:
193 // Does nothing.
194 return null;
195 // Error cases
196 case IndeterminateComponent:
197 throw new Error('An indeterminate component should have become determinate before completing.');
198 default:
199 throw new Error('Unknown unit of work tag');
200 }
201 }
202
203 return {
204 completeWork: completeWork
205 };
206};
\No newline at end of file