Use queue for pendingState
Changes the type of pendingState to be a linked list of partial state objects.
Andrew Clark committed
Jul 24, 2016 at 22:49 UTC
8b200c5e7822b137029db0634094f69d790f2272
5 files changed
+122
-28
src/renderers/shared/fiber/ReactFiber.js
+6
-4
@@ -15,6 +15,7 @@
15
import type { ReactCoroutine, ReactYield } from 'ReactCoroutine';
16
import type { TypeOfWork } from 'ReactTypeOfWork';
17
import type { PriorityLevel } from 'ReactPriorityLevel';
18
+import type { PendingState } from 'ReactFiberPendingState';
19
20
var ReactTypeOfWork = require('ReactTypeOfWork');
21
var {
@@ -76,10 +77,11 @@ export type Fiber = Instance & {
77
pendingProps: any, // This type will be more specific once we overload the tag.
78
// TODO: I think that there is a way to merge pendingProps and memoizedProps.
79
memoizedProps: any, // The props used to create the output.
79
- // Local state for class components. May need better naming to disambiguate
80
- // from stateNode.
81
- pendingState: any,
82
- memoizedState: any, // The state used to create the output.
80
+ // Local state for class components. Either null or a linked list of partial
81
+ // state objects.
82
+ pendingState: PendingState,
83
+ // The state used to create the output. This is a full state object.
84
+ memoizedState: any,
85
// Output is the return value of this fiber, or a linked list of return values
86
// if this returns multiple values. Such as a fragment.
87
output: any, // This type will be more specific once we overload the tag.
src/renderers/shared/fiber/ReactFiberBeginWork.js
+40
-17
@@ -18,6 +18,7 @@ import type { FiberRoot } from 'ReactFiberRoot';
18
import type { HostConfig } from 'ReactFiberReconciler';
19
import type { Scheduler } from 'ReactFiberScheduler';
20
import type { PriorityLevel } from 'ReactPriorityLevel';
21
+import type { PendingState } from 'ReactFiberPendingState';
22
23
var {
24
reconcileChildFibers,
@@ -40,6 +41,10 @@ var {
41
NoWork,
42
OffscreenPriority,
43
} = require('ReactPriorityLevel');
44
+var {
45
+ createPendingState,
46
+ mergePendingState,
47
+} = require('ReactFiberPendingState');
48
49
module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>, getScheduler: () => Scheduler) {
50
@@ -108,10 +113,9 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>, getSchedu
113
return workInProgress.child;
114
}
115
111
- function updateFiber(fiber: Fiber, state: any, priorityLevel : PriorityLevel): void {
116
+ function scheduleUpdate(fiber: Fiber, pendingState: PendingState, priorityLevel : PriorityLevel): void {
117
const { scheduleLowPriWork } = getScheduler();
113
- fiber.pendingState = state;
114
-
118
+ fiber.pendingState = pendingState;
119
while (true) {
120
if (fiber.pendingWorkPriority === NoWork ||
121
fiber.pendingWorkPriority >= priorityLevel) {
@@ -134,37 +138,55 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>, getSchedu
138
const updater = {
139
enqueueSetState(instance, partialState) {
140
const fiber = instance._fiber;
137
-
138
- const prevState = fiber.pendingState || fiber.memoizedState;
139
- const state = Object.assign({}, prevState, partialState);
141
+ let pendingState = fiber.pendingState;
142
+
143
+ // Append to pending state queue
144
+ const nextPendingStateNode = createPendingState(partialState);
145
+ if (pendingState === null) {
146
+ pendingState = nextPendingStateNode;
147
+ } else {
148
+ if (pendingState.tail === null) {
149
+ pendingState.next = nextPendingStateNode;
150
+ } else {
151
+ pendingState.tail.next = nextPendingStateNode;
152
+ }
153
+ pendingState.tail = nextPendingStateNode;
154
+ }
155
156
// Must schedule an update on both alternates, because we don't know tree
157
// is current.
143
- updateFiber(fiber, state, LowPriority);
158
+ scheduleUpdate(fiber, pendingState, LowPriority);
159
if (fiber.alternate) {
145
- updateFiber(fiber.alternate, state, LowPriority);
160
+ scheduleUpdate(fiber.alternate, pendingState, LowPriority);
161
}
162
},
163
};
164
165
function updateClassComponent(current : ?Fiber, workInProgress : Fiber) {
166
// A class component update is the result of either new props or new state.
152
- // Account for the possibly of missing pending props or state by falling
153
- // back to the most recent props or state.
167
+ // Account for the possibly of missing pending props by falling back to the
168
+ // memoized props.
169
var props = workInProgress.pendingProps;
155
- var state = workInProgress.pendingState;
170
if (!props && current) {
171
props = current.memoizedProps;
172
}
159
- if (!state && current) {
160
- state = current.memoizedState;
173
+ // Compute the state using the memoized state and the pending state queue.
174
+ var pendingState = workInProgress.pendingState;
175
+ var state;
176
+ if (!current) {
177
+ state = mergePendingState(null, pendingState);
178
+ } else {
179
+ state = mergePendingState(current.memoizedState, pendingState);
180
}
181
182
var instance = workInProgress.stateNode;
183
if (!instance) {
184
var ctor = workInProgress.type;
185
workInProgress.stateNode = instance = new ctor(props);
167
- state = workInProgress.pendingState = instance.state || null;
186
+ state = instance.state || null;
187
+ // The initial state must be added to the pending state queue in case
188
+ // setState is called before the initial render.
189
+ workInProgress.pendingState = createPendingState(state);
190
// The instance needs access to the fiber so that it can schedule updates
191
instance._fiber = workInProgress;
192
instance.updater = updater;
@@ -310,11 +332,12 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>, getSchedu
332
workInProgress.child = workInProgress.progressedChild;
333
}
334
313
- if (workInProgress.pendingProps === null || (
335
+ if ((workInProgress.pendingProps === null || (
336
workInProgress.memoizedProps !== null &&
337
workInProgress.pendingProps === workInProgress.memoizedProps &&
316
- workInProgress.pendingState === workInProgress.memoizedState
317
- )) {
338
+ )) &&
339
+ workInProgress.pendingState === null
340
+ ) {
341
return bailoutOnAlreadyFinishedWork(current, workInProgress);
342
}
343
src/renderers/shared/fiber/ReactFiberCompleteWork.js
+4
-1
@@ -67,7 +67,6 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
67
// the linked list of fibers that has the individual output values.
68
returnFiber.output = (child && !child.sibling) ? child.output : child;
69
returnFiber.memoizedProps = returnFiber.pendingProps;
70
- returnFiber.memoizedState = returnFiber.pendingState;
70
}
71
72
function recursivelyFillYields(yields, output : ?Fiber | ?ReifiedYield) {
@@ -133,6 +132,10 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
132
return null;
133
case ClassComponent:
134
transferOutput(workInProgress.child, workInProgress);
135
+ // Don't use the pending state queue to compute the memoized state. We
136
+ // already merged it and assigned it to the instance. Copy it from there.
137
+ const state = workInProgress.stateNode.state;
138
+ workInProgress.memoizedState = state;
139
return null;
140
case HostContainer:
141
transferOutput(workInProgress.child, workInProgress);
src/renderers/shared/fiber/ReactFiberPendingState.js
new
+38
@@ -0,0 +1,38 @@
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
+ * @providesModule ReactFiberPendingState
10
+ * @flow
11
+ */
12
+
13
+'use strict';
14
+
15
+export type PendingState = {
16
+ partialState: any,
17
+ next: PendingState,
18
+ tail: PendingState
19
+} | null;
20
+
21
+exports.createPendingState = function(partialState: mixed): PendingState {
22
+ return {
23
+ partialState,
24
+ next: null,
25
+ tail: null,
26
+ };
27
+};
28
+
29
+exports.mergePendingState = function(prevState: any, queue: PendingState): any {
30
+ if (queue === null) {
31
+ return prevState;
32
+ }
33
+ let state = Object.assign({}, prevState, queue.partialState);
34
+ while (queue = queue.next) {
35
+ state = Object.assign(state, queue.partialState);
36
+ }
37
+ return state;
38
+};
src/renderers/shared/fiber/__tests__/ReactIncremental-test.js
+34
-6
@@ -559,16 +559,42 @@ describe('ReactIncremental', () => {
559
560
it('can update in the middle of a tree using setState', () => {
561
let instance;
562
- let states = [];
562
+ class Bar extends React.Component {
563
+ constructor() {
564
+ super();
565
+ this.state = { a: 'a' };
566
+ instance = this;
567
+ }
568
+ render() {
569
+ return <div>{this.props.children}</div>;
570
+ }
571
+ }
572
573
+ function Foo() {
574
+ return (
575
+ <div>
576
+ <Bar />
577
+ </div>
578
+ );
579
+ }
580
+
581
+ ReactNoop.render(<Foo />);
582
+ ReactNoop.flush();
583
+ expect(instance.state).toEqual({ a: 'a' });
584
+ instance.setState({ b: 'b' });
585
+ ReactNoop.flush();
586
+ expect(instance.state).toEqual({ a: 'a', b: 'b' });
587
+ });
588
+
589
+ it('can queue multiple state updates', () => {
590
+ let instance;
591
class Bar extends React.Component {
592
constructor() {
593
super();
567
- this.state = { string: 'a' };
594
+ this.state = { a: 'a' };
595
instance = this;
596
}
597
render() {
571
- states.push(this.state.string);
598
return <div>{this.props.children}</div>;
599
}
600
}
@@ -583,9 +609,11 @@ describe('ReactIncremental', () => {
609
610
ReactNoop.render(<Foo />);
611
ReactNoop.flush();
586
- expect(states).toEqual(['a']);
587
- instance.setState({ string: 'b' });
612
+ // Call setState multiple times before flushing
613
+ instance.setState({ b: 'b' });
614
+ instance.setState({ c: 'c' });
615
+ instance.setState({ d: 'd' });
616
ReactNoop.flush();
589
- expect(states).toEqual(['a', 'b']);
617
+ expect(instance.state).toEqual({ a: 'a', b: 'b', c: 'c', d: 'd' });
618
});
619
});