diff --git a/src/renderers/shared/fiber/ReactFiber.js b/src/renderers/shared/fiber/ReactFiber.js index e5ec0b6b09..33128d85dd 100644 --- a/src/renderers/shared/fiber/ReactFiber.js +++ b/src/renderers/shared/fiber/ReactFiber.js @@ -15,7 +15,7 @@ import type { ReactCoroutine, ReactYield } from 'ReactCoroutine'; import type { TypeOfWork } from 'ReactTypeOfWork'; import type { PriorityLevel } from 'ReactPriorityLevel'; -import type { PendingState } from 'ReactFiberPendingState'; +import type { StateQueue } from 'ReactFiberStateQueue'; var ReactTypeOfWork = require('ReactTypeOfWork'); var { @@ -77,9 +77,8 @@ export type Fiber = Instance & { pendingProps: any, // This type will be more specific once we overload the tag. // TODO: I think that there is a way to merge pendingProps and memoizedProps. memoizedProps: any, // The props used to create the output. - // Local state for class components. Either null or a linked list of partial - // state objects. - pendingState: PendingState, + // A queue of local state updates. + stateQueue: StateQueue, // The state used to create the output. This is a full state object. memoizedState: any, // 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 { pendingProps: null, memoizedProps: null, - pendingState: null, + stateQueue: null, memoizedState: null, output: null, @@ -200,7 +199,7 @@ exports.cloneFiber = function(fiber : Fiber, priorityLevel : PriorityLevel) : Fi alt.sibling = fiber.sibling; // This should always be overridden. TODO: null alt.ref = fiber.ref; alt.pendingProps = fiber.pendingProps; // TODO: Pass as argument. - alt.pendingState = fiber.pendingState; + alt.stateQueue = fiber.stateQueue; alt.pendingWorkPriority = priorityLevel; alt.child = fiber.child; @@ -226,7 +225,7 @@ exports.cloneFiber = function(fiber : Fiber, priorityLevel : PriorityLevel) : Fi // pendingProps is here for symmetry but is unnecessary in practice for now. // TODO: Pass in the new pendingProps as an argument maybe? alt.pendingProps = fiber.pendingProps; - alt.pendingState = fiber.pendingState; + alt.stateQueue = fiber.stateQueue; alt.pendingWorkPriority = priorityLevel; alt.memoizedProps = fiber.memoizedProps; diff --git a/src/renderers/shared/fiber/ReactFiberBeginWork.js b/src/renderers/shared/fiber/ReactFiberBeginWork.js index fb061c60d3..ceecedb322 100644 --- a/src/renderers/shared/fiber/ReactFiberBeginWork.js +++ b/src/renderers/shared/fiber/ReactFiberBeginWork.js @@ -18,7 +18,7 @@ import type { FiberRoot } from 'ReactFiberRoot'; import type { HostConfig } from 'ReactFiberReconciler'; import type { Scheduler } from 'ReactFiberScheduler'; import type { PriorityLevel } from 'ReactPriorityLevel'; -import type { PendingState } from 'ReactFiberPendingState'; +import type { StateQueue } from 'ReactFiberStateQueue'; var { reconcileChildFibers, @@ -42,11 +42,12 @@ var { OffscreenPriority, } = require('ReactPriorityLevel'); var { - createPendingState, - mergePendingState, -} = require('ReactFiberPendingState'); + createStateQueue, + addToQueue, + mergeStateQueue, +} = require('ReactFiberStateQueue'); -module.exports = function(config : HostConfig, getScheduler: () => Scheduler) { +module.exports = function(config : HostConfig, getScheduler : () => Scheduler) { function markChildAsProgressed(current, workInProgress, priorityLevel) { // We now have clones. Let's store them as the currently progressed work. @@ -113,9 +114,9 @@ module.exports = function(config : HostConfig, getSchedu return workInProgress.child; } - function scheduleUpdate(fiber: Fiber, pendingState: PendingState, priorityLevel : PriorityLevel): void { + function scheduleUpdate(fiber: Fiber, stateQueue: StateQueue, priorityLevel : PriorityLevel): void { const { scheduleLowPriWork } = getScheduler(); - fiber.pendingState = pendingState; + fiber.stateQueue = stateQueue; while (true) { if (fiber.pendingWorkPriority === NoWork || fiber.pendingWorkPriority >= priorityLevel) { @@ -138,26 +139,20 @@ module.exports = function(config : HostConfig, getSchedu const updater = { enqueueSetState(instance, partialState) { const fiber = instance._fiber; - let pendingState = fiber.pendingState; + let stateQueue = fiber.stateQueue; // Append to pending state queue - const nextPendingStateNode = createPendingState(partialState); - if (pendingState === null) { - pendingState = nextPendingStateNode; + if (stateQueue === null) { + stateQueue = createStateQueue(partialState); } else { - if (pendingState.tail === null) { - pendingState.next = nextPendingStateNode; - } else { - pendingState.tail.next = nextPendingStateNode; - } - pendingState.tail = nextPendingStateNode; + addToQueue(stateQueue, partialState); } // Must schedule an update on both alternates, because we don't know tree // is current. - scheduleUpdate(fiber, pendingState, LowPriority); + scheduleUpdate(fiber, stateQueue, LowPriority); if (fiber.alternate) { - scheduleUpdate(fiber.alternate, pendingState, LowPriority); + scheduleUpdate(fiber.alternate, stateQueue, LowPriority); } }, }; @@ -171,12 +166,12 @@ module.exports = function(config : HostConfig, getSchedu props = current.memoizedProps; } // Compute the state using the memoized state and the pending state queue. - var pendingState = workInProgress.pendingState; + var stateQueue = workInProgress.stateQueue; var state; if (!current) { - state = mergePendingState(null, pendingState); + state = mergeStateQueue(null, props, stateQueue); } else { - state = mergePendingState(current.memoizedState, pendingState); + state = mergeStateQueue(current.memoizedState, props, stateQueue); } var instance = workInProgress.stateNode; @@ -186,7 +181,7 @@ module.exports = function(config : HostConfig, getSchedu state = instance.state || null; // The initial state must be added to the pending state queue in case // setState is called before the initial render. - workInProgress.pendingState = createPendingState(state); + workInProgress.stateQueue = createStateQueue(state); // The instance needs access to the fiber so that it can schedule updates instance._fiber = workInProgress; instance.updater = updater; @@ -334,9 +329,9 @@ module.exports = function(config : HostConfig, getSchedu if ((workInProgress.pendingProps === null || ( workInProgress.memoizedProps !== null && - workInProgress.pendingProps === workInProgress.memoizedProps && + workInProgress.pendingProps === workInProgress.memoizedProps )) && - workInProgress.pendingState === null + workInProgress.stateQueue === null ) { return bailoutOnAlreadyFinishedWork(current, workInProgress); } diff --git a/src/renderers/shared/fiber/ReactFiberCompleteWork.js b/src/renderers/shared/fiber/ReactFiberCompleteWork.js index 10f0571186..fdeb1f5e56 100644 --- a/src/renderers/shared/fiber/ReactFiberCompleteWork.js +++ b/src/renderers/shared/fiber/ReactFiberCompleteWork.js @@ -132,8 +132,8 @@ module.exports = function(config : HostConfig) { return null; case ClassComponent: transferOutput(workInProgress.child, workInProgress); - // Don't use the pending state queue to compute the memoized state. We - // already merged it and assigned it to the instance. Copy it from there. + // Don't use the state queue to compute the memoized state. We already + // merged it and assigned it to the instance. Copy it from there. const state = workInProgress.stateNode.state; workInProgress.memoizedState = state; return null; diff --git a/src/renderers/shared/fiber/ReactFiberPendingState.js b/src/renderers/shared/fiber/ReactFiberPendingState.js deleted file mode 100644 index 10c6615b7c..0000000000 --- a/src/renderers/shared/fiber/ReactFiberPendingState.js +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright 2013-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule ReactFiberPendingState - * @flow - */ - -'use strict'; - -export type PendingState = { - partialState: any, - next: PendingState, - tail: PendingState -} | null; - -exports.createPendingState = function(partialState: mixed): PendingState { - return { - partialState, - next: null, - tail: null, - }; -}; - -exports.mergePendingState = function(prevState: any, queue: PendingState): any { - if (queue === null) { - return prevState; - } - let state = Object.assign({}, prevState, queue.partialState); - while (queue = queue.next) { - state = Object.assign(state, queue.partialState); - } - return state; -}; diff --git a/src/renderers/shared/fiber/ReactFiberScheduler.js b/src/renderers/shared/fiber/ReactFiberScheduler.js index 4f8b017a9d..2136907d1a 100644 --- a/src/renderers/shared/fiber/ReactFiberScheduler.js +++ b/src/renderers/shared/fiber/ReactFiberScheduler.js @@ -143,9 +143,9 @@ module.exports = function(config : HostConfig) { // The work is now done. We don't need this anymore. This flags // to the system not to redo any work here. workInProgress.pendingProps = null; - workInProgress.pendingState = null; + workInProgress.stateQueue = null; if (current) { - current.pendingState = null; + current.stateQueue = null; } const returnFiber = workInProgress.return; diff --git a/src/renderers/shared/fiber/ReactFiberStateQueue.js b/src/renderers/shared/fiber/ReactFiberStateQueue.js new file mode 100644 index 0000000000..e8d6c7385e --- /dev/null +++ b/src/renderers/shared/fiber/ReactFiberStateQueue.js @@ -0,0 +1,51 @@ +/** + * Copyright 2013-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactFiberStateQueue + * @flow + */ + +'use strict'; + +export type StateQueue = { + partialState: any, + next: StateQueue, + tail: StateQueue +} | null; + +exports.createStateQueue = function(partialState : mixed) : StateQueue { + return { + partialState, + next: null, + tail: null, + }; +}; + +exports.addToQueue = function(queue : StateQueue, partialState : mixed) { + const node = exports.createStateQueue(partialState); + if (queue.tail === null) { + queue.next = node; + } else { + queue.tail.next = node; + } + queue.tail = node; +} + +exports.mergeStateQueue = function(prevState : any, props : any, queue : StateQueue) : any { + if (queue === null) { + return prevState; + } + let state = Object.assign({}, prevState); + do { + const partialState = typeof queue.partialState === 'function' ? + queue.partialState(state, props) : + queue.partialState; + state = Object.assign(state, partialState); + } while (queue = queue.next); + return state; +}; diff --git a/src/renderers/shared/fiber/__tests__/ReactIncremental-test.js b/src/renderers/shared/fiber/__tests__/ReactIncremental-test.js index c792e9e2a7..e7dccc7603 100644 --- a/src/renderers/shared/fiber/__tests__/ReactIncremental-test.js +++ b/src/renderers/shared/fiber/__tests__/ReactIncremental-test.js @@ -616,4 +616,41 @@ describe('ReactIncremental', () => { ReactNoop.flush(); expect(instance.state).toEqual({ a: 'a', b: 'b', c: 'c', d: 'd' }); }); + + it('can use updater form of setState', () => { + let instance; + class Bar extends React.Component { + constructor() { + super(); + this.state = { num: 1 }; + instance = this; + } + render() { + return
{this.props.children}
; + } + } + + function Foo({ multiplier }) { + return ( +
+ +
+ ); + } + + function updater(state, props) { + return { num: state.num * props.multiplier }; + } + + ReactNoop.render(); + ReactNoop.flush(); + expect(instance.state.num).toEqual(1); + instance.setState(updater); + ReactNoop.flush(); + expect(instance.state.num).toEqual(2); + ReactNoop.render(); + instance.setState(updater); + ReactNoop.flush(); + expect(instance.state.num).toEqual(6); + }); });