@samitouri / QOS-React / commits / 8b200c5e78

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 });