Skip to content

Commit da9a658

Browse files
sebmarkbagezpao
authored andcommitted
Merge pull request #7344 from acdlite/fibersetstate
[Fiber] setState (cherry picked from commit 3e54b28)
1 parent 3bc9d9c commit da9a658

File tree

10 files changed

+524
-16
lines changed

10 files changed

+524
-16
lines changed

src/renderers/noop/ReactNoop.js

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
'use strict';
2121

2222
import type { Fiber } from 'ReactFiber';
23+
import type { UpdateQueue } from 'ReactFiberUpdateQueue';
2324
import type { HostChildren } from 'ReactFiberReconciler';
2425

2526
var ReactFiberReconciler = require('ReactFiberReconciler');
@@ -153,30 +154,61 @@ var ReactNoop = {
153154
return;
154155
}
155156

157+
var bufferedLog = [];
158+
function log(...args) {
159+
bufferedLog.push(...args, '\n');
160+
}
161+
156162
function logHostInstances(children: Array<Instance>, depth) {
157163
for (var i = 0; i < children.length; i++) {
158164
var child = children[i];
159-
console.log(' '.repeat(depth) + '- ' + child.type + '#' + child.id);
165+
log(' '.repeat(depth) + '- ' + child.type + '#' + child.id);
160166
logHostInstances(child.children, depth + 1);
161167
}
162168
}
163169
function logContainer(container : Container, depth) {
164-
console.log(' '.repeat(depth) + '- [root#' + container.rootID + ']');
170+
log(' '.repeat(depth) + '- [root#' + container.rootID + ']');
165171
logHostInstances(container.children, depth + 1);
166172
}
167173

174+
function logUpdateQueue(updateQueue : UpdateQueue, depth) {
175+
log(
176+
' '.repeat(depth + 1) + 'QUEUED UPDATES',
177+
updateQueue.isReplace ? 'is replace' : '',
178+
updateQueue.isForced ? 'is forced' : ''
179+
);
180+
log(
181+
' '.repeat(depth + 1) + '~',
182+
updateQueue.partialState,
183+
updateQueue.callback ? 'with callback' : ''
184+
);
185+
var next;
186+
while (next = updateQueue.next) {
187+
log(
188+
' '.repeat(depth + 1) + '~',
189+
next.partialState,
190+
next.callback ? 'with callback' : ''
191+
);
192+
}
193+
}
194+
168195
function logFiber(fiber : Fiber, depth) {
169-
console.log(
196+
log(
170197
' '.repeat(depth) + '- ' + (fiber.type ? fiber.type.name || fiber.type : '[root]'),
171198
'[' + fiber.pendingWorkPriority + (fiber.pendingProps ? '*' : '') + ']'
172199
);
200+
if (fiber.updateQueue) {
201+
logUpdateQueue(fiber.updateQueue, depth);
202+
}
173203
const childInProgress = fiber.progressedChild;
174204
if (childInProgress && childInProgress !== fiber.child) {
175-
console.log(' '.repeat(depth + 1) + 'IN PROGRESS: ' + fiber.progressedPriority);
205+
log(' '.repeat(depth + 1) + 'IN PROGRESS: ' + fiber.progressedPriority);
176206
logFiber(childInProgress, depth + 1);
177207
if (fiber.child) {
178-
console.log(' '.repeat(depth + 1) + 'CURRENT');
208+
log(' '.repeat(depth + 1) + 'CURRENT');
179209
}
210+
} else if (fiber.child && fiber.updateQueue) {
211+
log(' '.repeat(depth + 1) + 'CHILDREN');
180212
}
181213
if (fiber.child) {
182214
logFiber(fiber.child, depth + 1);
@@ -186,10 +218,12 @@ var ReactNoop = {
186218
}
187219
}
188220

189-
console.log('HOST INSTANCES:');
221+
log('HOST INSTANCES:');
190222
logContainer(rootContainer, 0);
191-
console.log('FIBERS:');
223+
log('FIBERS:');
192224
logFiber((root.stateNode : any).current, 0);
225+
226+
console.log(...bufferedLog);
193227
},
194228

195229
};

src/renderers/shared/fiber/ReactFiber.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import type { ReactCoroutine, ReactYield } from 'ReactCoroutine';
1616
import type { TypeOfWork } from 'ReactTypeOfWork';
1717
import type { PriorityLevel } from 'ReactPriorityLevel';
18+
import type { UpdateQueue } from 'ReactFiberUpdateQueue';
1819

1920
var ReactTypeOfWork = require('ReactTypeOfWork');
2021
var {
@@ -76,6 +77,12 @@ export type Fiber = Instance & {
7677
pendingProps: any, // This type will be more specific once we overload the tag.
7778
// TODO: I think that there is a way to merge pendingProps and memoizedProps.
7879
memoizedProps: any, // The props used to create the output.
80+
// A queue of local state updates.
81+
updateQueue: ?UpdateQueue,
82+
// The state used to create the output. This is a full state object.
83+
memoizedState: any,
84+
// Linked list of callbacks to call after updates are committed.
85+
callbackList: ?UpdateQueue,
7986
// Output is the return value of this fiber, or a linked list of return values
8087
// if this returns multiple values. Such as a fragment.
8188
output: any, // This type will be more specific once we overload the tag.
@@ -151,6 +158,9 @@ var createFiber = function(tag : TypeOfWork, key : null | string) : Fiber {
151158

152159
pendingProps: null,
153160
memoizedProps: null,
161+
updateQueue: null,
162+
memoizedState: null,
163+
callbackList: null,
154164
output: null,
155165

156166
nextEffect: null,
@@ -192,6 +202,8 @@ exports.cloneFiber = function(fiber : Fiber, priorityLevel : PriorityLevel) : Fi
192202
alt.sibling = fiber.sibling; // This should always be overridden. TODO: null
193203
alt.ref = fiber.ref;
194204
alt.pendingProps = fiber.pendingProps; // TODO: Pass as argument.
205+
alt.updateQueue = fiber.updateQueue;
206+
alt.callbackList = fiber.callbackList;
195207
alt.pendingWorkPriority = priorityLevel;
196208

197209
alt.child = fiber.child;
@@ -217,6 +229,8 @@ exports.cloneFiber = function(fiber : Fiber, priorityLevel : PriorityLevel) : Fi
217229
// pendingProps is here for symmetry but is unnecessary in practice for now.
218230
// TODO: Pass in the new pendingProps as an argument maybe?
219231
alt.pendingProps = fiber.pendingProps;
232+
alt.updateQueue = fiber.updateQueue;
233+
alt.callbackList = fiber.callbackList;
220234
alt.pendingWorkPriority = priorityLevel;
221235

222236
alt.memoizedProps = fiber.memoizedProps;

src/renderers/shared/fiber/ReactFiberBeginWork.js

Lines changed: 108 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,18 @@
1414

1515
import type { ReactCoroutine } from 'ReactCoroutine';
1616
import type { Fiber } from 'ReactFiber';
17+
import type { FiberRoot } from 'ReactFiberRoot';
1718
import type { HostConfig } from 'ReactFiberReconciler';
19+
import type { Scheduler } from 'ReactFiberScheduler';
1820
import type { PriorityLevel } from 'ReactPriorityLevel';
21+
import type { UpdateQueue } from 'ReactFiberUpdateQueue';
1922

2023
var {
2124
reconcileChildFibers,
2225
reconcileChildFibersInPlace,
2326
cloneChildFibers,
2427
} = require('ReactChildFiber');
28+
var { LowPriority } = require('ReactPriorityLevel');
2529
var ReactTypeOfWork = require('ReactTypeOfWork');
2630
var {
2731
IndeterminateComponent,
@@ -37,8 +41,15 @@ var {
3741
NoWork,
3842
OffscreenPriority,
3943
} = require('ReactPriorityLevel');
44+
var {
45+
createUpdateQueue,
46+
addToQueue,
47+
addCallbackToQueue,
48+
mergeUpdateQueue,
49+
} = require('ReactFiberUpdateQueue');
50+
var ReactInstanceMap = require('ReactInstanceMap');
4051

41-
module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
52+
module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>, getScheduler : () => Scheduler) {
4253

4354
function markChildAsProgressed(current, workInProgress, priorityLevel) {
4455
// We now have clones. Let's store them as the currently progressed work.
@@ -105,25 +116,116 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
105116
return workInProgress.child;
106117
}
107118

119+
function scheduleUpdate(fiber: Fiber, updateQueue: UpdateQueue, priorityLevel : PriorityLevel): void {
120+
const { scheduleLowPriWork } = getScheduler();
121+
fiber.updateQueue = updateQueue;
122+
// Schedule update on the alternate as well, since we don't know which tree
123+
// is current.
124+
if (fiber.alternate) {
125+
fiber.alternate.updateQueue = updateQueue;
126+
}
127+
while (true) {
128+
if (fiber.pendingWorkPriority === NoWork ||
129+
fiber.pendingWorkPriority >= priorityLevel) {
130+
fiber.pendingWorkPriority = priorityLevel;
131+
}
132+
if (fiber.alternate) {
133+
if (fiber.alternate.pendingWorkPriority === NoWork ||
134+
fiber.alternate.pendingWorkPriority >= priorityLevel) {
135+
fiber.alternate.pendingWorkPriority = priorityLevel;
136+
}
137+
}
138+
// Duck type root
139+
if (fiber.stateNode && fiber.stateNode.containerInfo) {
140+
const root : FiberRoot = (fiber.stateNode : any);
141+
scheduleLowPriWork(root, priorityLevel);
142+
return;
143+
}
144+
if (!fiber.return) {
145+
throw new Error('No root!');
146+
}
147+
fiber = fiber.return;
148+
}
149+
}
150+
151+
// Class component state updater
152+
const updater = {
153+
enqueueSetState(instance, partialState) {
154+
const fiber = ReactInstanceMap.get(instance);
155+
const updateQueue = fiber.updateQueue ?
156+
addToQueue(fiber.updateQueue, partialState) :
157+
createUpdateQueue(partialState);
158+
scheduleUpdate(fiber, updateQueue, LowPriority);
159+
},
160+
enqueueReplaceState(instance, state) {
161+
const fiber = ReactInstanceMap.get(instance);
162+
const updateQueue = createUpdateQueue(state);
163+
updateQueue.isReplace = true;
164+
scheduleUpdate(fiber, updateQueue, LowPriority);
165+
},
166+
enqueueForceUpdate(instance) {
167+
const fiber = ReactInstanceMap.get(instance);
168+
const updateQueue = fiber.updateQueue || createUpdateQueue(null);
169+
updateQueue.isForced = true;
170+
scheduleUpdate(fiber, updateQueue, LowPriority);
171+
},
172+
enqueueCallback(instance, callback) {
173+
const fiber = ReactInstanceMap.get(instance);
174+
let updateQueue = fiber.updateQueue ?
175+
fiber.updateQueue :
176+
createUpdateQueue(null);
177+
addCallbackToQueue(updateQueue, callback);
178+
fiber.updateQueue = updateQueue;
179+
if (fiber.alternate) {
180+
fiber.alternate.updateQueue = updateQueue;
181+
}
182+
},
183+
};
184+
108185
function updateClassComponent(current : ?Fiber, workInProgress : Fiber) {
186+
// A class component update is the result of either new props or new state.
187+
// Account for the possibly of missing pending props by falling back to the
188+
// memoized props.
109189
var props = workInProgress.pendingProps;
190+
if (!props && current) {
191+
props = current.memoizedProps;
192+
}
193+
// Compute the state using the memoized state and the update queue.
194+
var updateQueue = workInProgress.updateQueue;
195+
var previousState = current ? current.memoizedState : null;
196+
var state = updateQueue ?
197+
mergeUpdateQueue(updateQueue, previousState, props) :
198+
previousState;
199+
110200
var instance = workInProgress.stateNode;
111201
if (!instance) {
112202
var ctor = workInProgress.type;
113203
workInProgress.stateNode = instance = new ctor(props);
114-
} else if (typeof instance.shouldComponentUpdate === 'function') {
204+
state = instance.state || null;
205+
// The initial state must be added to the update queue in case
206+
// setState is called before the initial render.
207+
if (state !== null) {
208+
workInProgress.updateQueue = createUpdateQueue(state);
209+
}
210+
// The instance needs access to the fiber so that it can schedule updates
211+
ReactInstanceMap.set(instance, workInProgress);
212+
instance.updater = updater;
213+
} else if (typeof instance.shouldComponentUpdate === 'function' &&
214+
!(updateQueue && updateQueue.isForced)) {
115215
if (workInProgress.memoizedProps !== null) {
116216
// Reset the props, in case this is a ping-pong case rather than a
117217
// completed update case. For the completed update case, the instance
118218
// props will already be the memoizedProps.
119219
instance.props = workInProgress.memoizedProps;
120-
if (!instance.shouldComponentUpdate(props)) {
220+
instance.state = workInProgress.memoizedState;
221+
if (!instance.shouldComponentUpdate(props, state)) {
121222
return bailoutOnAlreadyFinishedWork(current, workInProgress);
122223
}
123224
}
124225
}
125226

126227
instance.props = props;
228+
instance.state = state;
127229
var nextChildren = instance.render();
128230
reconcileChildren(current, workInProgress, nextChildren);
129231

@@ -251,10 +353,11 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
251353
workInProgress.child = workInProgress.progressedChild;
252354
}
253355

254-
if (workInProgress.pendingProps === null || (
356+
if ((workInProgress.pendingProps === null || (
255357
workInProgress.memoizedProps !== null &&
256358
workInProgress.pendingProps === workInProgress.memoizedProps
257-
)) {
359+
)) &&
360+
workInProgress.updateQueue === null) {
258361
return bailoutOnAlreadyFinishedWork(current, workInProgress);
259362
}
260363

src/renderers/shared/fiber/ReactFiberCommitWork.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ var {
2222
HostContainer,
2323
HostComponent,
2424
} = ReactTypeOfWork;
25+
var { callCallbacks } = require('ReactFiberUpdateQueue');
2526

2627
module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
2728

@@ -31,6 +32,18 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
3132
function commitWork(current : ?Fiber, finishedWork : Fiber) : void {
3233
switch (finishedWork.tag) {
3334
case ClassComponent: {
35+
// Clear updates from current fiber. This must go before the callbacks
36+
// are reset, in case an update is triggered from inside a callback. Is
37+
// this safe? Relies on the assumption that work is only committed if
38+
// the update queue is empty.
39+
if (finishedWork.alternate) {
40+
finishedWork.alternate.updateQueue = null;
41+
}
42+
if (finishedWork.callbackList) {
43+
const { callbackList } = finishedWork;
44+
finishedWork.callbackList = null;
45+
callCallbacks(callbackList, finishedWork.stateNode);
46+
}
3447
// TODO: Fire componentDidMount/componentDidUpdate, update refs
3548
return;
3649
}

src/renderers/shared/fiber/ReactFiberCompleteWork.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
4646
}
4747
}
4848

49-
/*
5049
// TODO: It's possible this will create layout thrash issues because mutations
5150
// of the DOM and life-cycles are interleaved. E.g. if a componentDidMount
5251
// of a sibling reads, then the next sibling updates and reads etc.
@@ -59,7 +58,6 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
5958
}
6059
workInProgress.lastEffect = workInProgress;
6160
}
62-
*/
6361

6462
function transferOutput(child : ?Fiber, returnFiber : Fiber) {
6563
// If we have a single result, we just pass that through as the output to
@@ -132,6 +130,17 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
132130
return null;
133131
case ClassComponent:
134132
transferOutput(workInProgress.child, workInProgress);
133+
// Don't use the state queue to compute the memoized state. We already
134+
// merged it and assigned it to the instance. Transfer it from there.
135+
// Also need to transfer the props, because pendingProps will be null
136+
// in the case of an update
137+
const { state, props } = workInProgress.stateNode;
138+
workInProgress.memoizedState = state;
139+
workInProgress.memoizedProps = props;
140+
// Transfer update queue to callbackList field so callbacks can be
141+
// called during commit phase.
142+
workInProgress.callbackList = workInProgress.updateQueue;
143+
markForPostEffect(workInProgress);
135144
return null;
136145
case HostContainer:
137146
transferOutput(workInProgress.child, workInProgress);

0 commit comments

Comments
 (0)