UNPKG

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