@samitouri / QOS-React / commits / 25d199abe0

Updater form of setState

Add support for setState((state, props) => newState). Rename pendingState to stateQueue.

Andrew Clark committed Jul 25, 2016 at 09:09 UTC 25d199abe01befe07bed0c7d1ae55af47857650c
7 files changed +118 -74
src/renderers/shared/fiber/ReactFiber.js
+6 -7
@@ -15,7 +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';
18 +import type { StateQueue } from 'ReactFiberStateQueue';
19
20 var ReactTypeOfWork = require('ReactTypeOfWork');
21 var {
@@ -77,9 +77,8 @@ 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.
80 - // Local state for class components. Either null or a linked list of partial
81 - // state objects.
82 - pendingState: PendingState,
80 + // A queue of local state updates.
81 + stateQueue: StateQueue,
82 // The state used to create the output. This is a full state object.
83 memoizedState: any,
84 // Output is the return value of this fiber, or a linked list of return values
@@ -157,7 +156,7 @@ var createFiber = function(tag : TypeOfWork, key : null | string) : Fiber {
156
157 pendingProps: null,
158 memoizedProps: null,
160 - pendingState: null,
159 + stateQueue: null,
160 memoizedState: null,
161 output: null,
162
@@ -200,7 +199,7 @@ exports.cloneFiber = function(fiber : Fiber, priorityLevel : PriorityLevel) : Fi
199 alt.sibling = fiber.sibling; // This should always be overridden. TODO: null
200 alt.ref = fiber.ref;
201 alt.pendingProps = fiber.pendingProps; // TODO: Pass as argument.
203 - alt.pendingState = fiber.pendingState;
202 + alt.stateQueue = fiber.stateQueue;
203 alt.pendingWorkPriority = priorityLevel;
204
205 alt.child = fiber.child;
@@ -226,7 +225,7 @@ exports.cloneFiber = function(fiber : Fiber, priorityLevel : PriorityLevel) : Fi
225 // pendingProps is here for symmetry but is unnecessary in practice for now.
226 // TODO: Pass in the new pendingProps as an argument maybe?
227 alt.pendingProps = fiber.pendingProps;
229 - alt.pendingState = fiber.pendingState;
228 + alt.stateQueue = fiber.stateQueue;
229 alt.pendingWorkPriority = priorityLevel;
230
231 alt.memoizedProps = fiber.memoizedProps;
src/renderers/shared/fiber/ReactFiberBeginWork.js
+20 -25
@@ -18,7 +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';
21 +import type { StateQueue } from 'ReactFiberStateQueue';
22
23 var {
24 reconcileChildFibers,
@@ -42,11 +42,12 @@ var {
42 OffscreenPriority,
43 } = require('ReactPriorityLevel');
44 var {
45 - createPendingState,
46 - mergePendingState,
47 -} = require('ReactFiberPendingState');
45 + createStateQueue,
46 + addToQueue,
47 + mergeStateQueue,
48 +} = require('ReactFiberStateQueue');
49
49 -module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>, getScheduler: () => Scheduler) {
50 +module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>, getScheduler : () => Scheduler) {
51
52 function markChildAsProgressed(current, workInProgress, priorityLevel) {
53 // We now have clones. Let's store them as the currently progressed work.
@@ -113,9 +114,9 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>, getSchedu
114 return workInProgress.child;
115 }
116
116 - function scheduleUpdate(fiber: Fiber, pendingState: PendingState, priorityLevel : PriorityLevel): void {
117 + function scheduleUpdate(fiber: Fiber, stateQueue: StateQueue, priorityLevel : PriorityLevel): void {
118 const { scheduleLowPriWork } = getScheduler();
118 - fiber.pendingState = pendingState;
119 + fiber.stateQueue = stateQueue;
120 while (true) {
121 if (fiber.pendingWorkPriority === NoWork ||
122 fiber.pendingWorkPriority >= priorityLevel) {
@@ -138,26 +139,20 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>, getSchedu
139 const updater = {
140 enqueueSetState(instance, partialState) {
141 const fiber = instance._fiber;
141 - let pendingState = fiber.pendingState;
142 + let stateQueue = fiber.stateQueue;
143
144 // Append to pending state queue
144 - const nextPendingStateNode = createPendingState(partialState);
145 - if (pendingState === null) {
146 - pendingState = nextPendingStateNode;
145 + if (stateQueue === null) {
146 + stateQueue = createStateQueue(partialState);
147 } else {
148 - if (pendingState.tail === null) {
149 - pendingState.next = nextPendingStateNode;
150 - } else {
151 - pendingState.tail.next = nextPendingStateNode;
152 - }
153 - pendingState.tail = nextPendingStateNode;
148 + addToQueue(stateQueue, partialState);
149 }
150
151 // Must schedule an update on both alternates, because we don't know tree
152 // is current.
158 - scheduleUpdate(fiber, pendingState, LowPriority);
153 + scheduleUpdate(fiber, stateQueue, LowPriority);
154 if (fiber.alternate) {
160 - scheduleUpdate(fiber.alternate, pendingState, LowPriority);
155 + scheduleUpdate(fiber.alternate, stateQueue, LowPriority);
156 }
157 },
158 };
@@ -171,12 +166,12 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>, getSchedu
166 props = current.memoizedProps;
167 }
168 // Compute the state using the memoized state and the pending state queue.
174 - var pendingState = workInProgress.pendingState;
169 + var stateQueue = workInProgress.stateQueue;
170 var state;
171 if (!current) {
177 - state = mergePendingState(null, pendingState);
172 + state = mergeStateQueue(null, props, stateQueue);
173 } else {
179 - state = mergePendingState(current.memoizedState, pendingState);
174 + state = mergeStateQueue(current.memoizedState, props, stateQueue);
175 }
176
177 var instance = workInProgress.stateNode;
@@ -186,7 +181,7 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>, getSchedu
181 state = instance.state || null;
182 // The initial state must be added to the pending state queue in case
183 // setState is called before the initial render.
189 - workInProgress.pendingState = createPendingState(state);
184 + workInProgress.stateQueue = createStateQueue(state);
185 // The instance needs access to the fiber so that it can schedule updates
186 instance._fiber = workInProgress;
187 instance.updater = updater;
@@ -334,9 +329,9 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>, getSchedu
329
330 if ((workInProgress.pendingProps === null || (
331 workInProgress.memoizedProps !== null &&
337 - workInProgress.pendingProps === workInProgress.memoizedProps &&
332 + workInProgress.pendingProps === workInProgress.memoizedProps
333 )) &&
339 - workInProgress.pendingState === null
334 + workInProgress.stateQueue === null
335 ) {
336 return bailoutOnAlreadyFinishedWork(current, workInProgress);
337 }
src/renderers/shared/fiber/ReactFiberCompleteWork.js
+2 -2
@@ -132,8 +132,8 @@ 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.
135 + // Don't use the state queue to compute the memoized state. We already
136 + // 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;
src/renderers/shared/fiber/ReactFiberPendingState.js deleted
-38
@@ -1,38 +0,0 @@
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/ReactFiberScheduler.js
+2 -2
@@ -143,9 +143,9 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
143 // The work is now done. We don't need this anymore. This flags
144 // to the system not to redo any work here.
145 workInProgress.pendingProps = null;
146 - workInProgress.pendingState = null;
146 + workInProgress.stateQueue = null;
147 if (current) {
148 - current.pendingState = null;
148 + current.stateQueue = null;
149 }
150
151 const returnFiber = workInProgress.return;
src/renderers/shared/fiber/ReactFiberStateQueue.js new
+51
@@ -0,0 +1,51 @@
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 ReactFiberStateQueue
10 + * @flow
11 + */
12 +
13 +'use strict';
14 +
15 +export type StateQueue = {
16 + partialState: any,
17 + next: StateQueue,
18 + tail: StateQueue
19 +} | null;
20 +
21 +exports.createStateQueue = function(partialState : mixed) : StateQueue {
22 + return {
23 + partialState,
24 + next: null,
25 + tail: null,
26 + };
27 +};
28 +
29 +exports.addToQueue = function(queue : StateQueue, partialState : mixed) {
30 + const node = exports.createStateQueue(partialState);
31 + if (queue.tail === null) {
32 + queue.next = node;
33 + } else {
34 + queue.tail.next = node;
35 + }
36 + queue.tail = node;
37 +}
38 +
39 +exports.mergeStateQueue = function(prevState : any, props : any, queue : StateQueue) : any {
40 + if (queue === null) {
41 + return prevState;
42 + }
43 + let state = Object.assign({}, prevState);
44 + do {
45 + const partialState = typeof queue.partialState === 'function' ?
46 + queue.partialState(state, props) :
47 + queue.partialState;
48 + state = Object.assign(state, partialState);
49 + } while (queue = queue.next);
50 + return state;
51 +};
src/renderers/shared/fiber/__tests__/ReactIncremental-test.js
+37
@@ -616,4 +616,41 @@ describe('ReactIncremental', () => {
616 ReactNoop.flush();
617 expect(instance.state).toEqual({ a: 'a', b: 'b', c: 'c', d: 'd' });
618 });
619 +
620 + it('can use updater form of setState', () => {
621 + let instance;
622 + class Bar extends React.Component {
623 + constructor() {
624 + super();
625 + this.state = { num: 1 };
626 + instance = this;
627 + }
628 + render() {
629 + return <div>{this.props.children}</div>;
630 + }
631 + }
632 +
633 + function Foo({ multiplier }) {
634 + return (
635 + <div>
636 + <Bar multiplier={multiplier} />
637 + </div>
638 + );
639 + }
640 +
641 + function updater(state, props) {
642 + return { num: state.num * props.multiplier };
643 + }
644 +
645 + ReactNoop.render(<Foo multiplier={2} />);
646 + ReactNoop.flush();
647 + expect(instance.state.num).toEqual(1);
648 + instance.setState(updater);
649 + ReactNoop.flush();
650 + expect(instance.state.num).toEqual(2);
651 + ReactNoop.render(<Foo multiplier={3} />);
652 + instance.setState(updater);
653 + ReactNoop.flush();
654 + expect(instance.state.num).toEqual(6);
655 + });
656 });