From 0f269053f7f002aa41f41ab629210c541ead000a Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Tue, 11 Oct 2016 10:43:47 -0700 Subject: [PATCH 1/3] Break out some Class Component logic into separate module Refactors the class logic a bit. I moved scheduleUpdate out into the scheduler since that's where the scheduling normally happens. I also moved it so that we can rely on hoisting to resolve the cycle statically. I moved the updater to a new class component file. I suspect we will need a bit of space in here since the class initialization code is quite complex. The class component dependency is currently fixed in BeginWork so we can't move complete or commit phase stuff to it. If we need to, we have to initialize it in the scheduler and pass it to the other phases. --- .../shared/fiber/ReactFiberBeginWork.js | 90 ++----------------- .../shared/fiber/ReactFiberClassComponent.js | 89 ++++++++++++++++++ .../shared/fiber/ReactFiberScheduler.js | 38 +++++--- .../shared/fiber/ReactFiberUpdateQueue.js | 8 +- 4 files changed, 128 insertions(+), 97 deletions(-) create mode 100644 src/renderers/shared/fiber/ReactFiberClassComponent.js diff --git a/src/renderers/shared/fiber/ReactFiberBeginWork.js b/src/renderers/shared/fiber/ReactFiberBeginWork.js index c03d7097382e..b9bac151de44 100644 --- a/src/renderers/shared/fiber/ReactFiberBeginWork.js +++ b/src/renderers/shared/fiber/ReactFiberBeginWork.js @@ -14,11 +14,8 @@ import type { ReactCoroutine } from 'ReactCoroutine'; import type { Fiber } from 'ReactFiber'; -import type { FiberRoot } from 'ReactFiberRoot'; import type { HostConfig } from 'ReactFiberReconciler'; -import type { Scheduler } from 'ReactFiberScheduler'; import type { PriorityLevel } from 'ReactPriorityLevel'; -import type { UpdateQueue } from 'ReactFiberUpdateQueue'; var { mountChildFibersInPlace, @@ -26,7 +23,6 @@ var { reconcileChildFibersInPlace, cloneChildFibers, } = require('ReactChildFiber'); -var { LowPriority } = require('ReactPriorityLevel'); var ReactTypeOfWork = require('ReactTypeOfWork'); var { IndeterminateComponent, @@ -45,17 +41,18 @@ var { OffscreenPriority, } = require('ReactPriorityLevel'); var { - createUpdateQueue, - addToQueue, - addCallbackToQueue, mergeUpdateQueue, } = require('ReactFiberUpdateQueue'); var { Placement, } = require('ReactTypeOfSideEffect'); -var ReactInstanceMap = require('ReactInstanceMap'); +var ReactFiberClassComponent = require('ReactFiberClassComponent'); -module.exports = function(config : HostConfig, getScheduler : () => Scheduler) { +module.exports = function(config : HostConfig, scheduleUpdate : (fiber: Fiber, priorityLevel : PriorityLevel) => void) { + + const { + mount, + } = ReactFiberClassComponent(scheduleUpdate); function markChildAsProgressed(current, workInProgress, priorityLevel) { // We now have clones. Let's store them as the currently progressed work. @@ -158,72 +155,6 @@ module.exports = function(config : HostConfig, g return workInProgress.child; } - function scheduleUpdate(fiber: Fiber, updateQueue: UpdateQueue, priorityLevel : PriorityLevel): void { - const { scheduleDeferredWork } = getScheduler(); - fiber.updateQueue = updateQueue; - // Schedule update on the alternate as well, since we don't know which tree - // is current. - if (fiber.alternate) { - fiber.alternate.updateQueue = updateQueue; - } - while (true) { - if (fiber.pendingWorkPriority === NoWork || - fiber.pendingWorkPriority >= priorityLevel) { - fiber.pendingWorkPriority = priorityLevel; - } - if (fiber.alternate) { - if (fiber.alternate.pendingWorkPriority === NoWork || - fiber.alternate.pendingWorkPriority >= priorityLevel) { - fiber.alternate.pendingWorkPriority = priorityLevel; - } - } - // Duck type root - if (fiber.stateNode && fiber.stateNode.containerInfo) { - const root : FiberRoot = (fiber.stateNode : any); - scheduleDeferredWork(root, priorityLevel); - return; - } - if (!fiber.return) { - throw new Error('No root!'); - } - fiber = fiber.return; - } - } - - // Class component state updater - const updater = { - enqueueSetState(instance, partialState) { - const fiber = ReactInstanceMap.get(instance); - const updateQueue = fiber.updateQueue ? - addToQueue(fiber.updateQueue, partialState) : - createUpdateQueue(partialState); - scheduleUpdate(fiber, updateQueue, LowPriority); - }, - enqueueReplaceState(instance, state) { - const fiber = ReactInstanceMap.get(instance); - const updateQueue = createUpdateQueue(state); - updateQueue.isReplace = true; - scheduleUpdate(fiber, updateQueue, LowPriority); - }, - enqueueForceUpdate(instance) { - const fiber = ReactInstanceMap.get(instance); - const updateQueue = fiber.updateQueue || createUpdateQueue(null); - updateQueue.isForced = true; - scheduleUpdate(fiber, updateQueue, LowPriority); - }, - enqueueCallback(instance, callback) { - const fiber = ReactInstanceMap.get(instance); - let updateQueue = fiber.updateQueue ? - fiber.updateQueue : - createUpdateQueue(null); - addCallbackToQueue(updateQueue, callback); - fiber.updateQueue = updateQueue; - if (fiber.alternate) { - fiber.alternate.updateQueue = updateQueue; - } - }, - }; - function updateClassComponent(current : ?Fiber, workInProgress : Fiber) { // A class component update is the result of either new props or new state. // Account for the possibly of missing pending props by falling back to the @@ -243,15 +174,8 @@ module.exports = function(config : HostConfig, g if (!instance) { var ctor = workInProgress.type; workInProgress.stateNode = instance = new ctor(props); + mount(workInProgress, instance); state = instance.state || null; - // The initial state must be added to the update queue in case - // setState is called before the initial render. - if (state !== null) { - workInProgress.updateQueue = createUpdateQueue(state); - } - // The instance needs access to the fiber so that it can schedule updates - ReactInstanceMap.set(instance, workInProgress); - instance.updater = updater; } else if (typeof instance.shouldComponentUpdate === 'function' && !(updateQueue && updateQueue.isForced)) { if (workInProgress.memoizedProps !== null) { diff --git a/src/renderers/shared/fiber/ReactFiberClassComponent.js b/src/renderers/shared/fiber/ReactFiberClassComponent.js new file mode 100644 index 000000000000..6110057607cb --- /dev/null +++ b/src/renderers/shared/fiber/ReactFiberClassComponent.js @@ -0,0 +1,89 @@ +/** + * 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 ReactFiberClassComponent + * @flow + */ + +'use strict'; + +import type { Fiber } from 'ReactFiber'; +import type { PriorityLevel } from 'ReactPriorityLevel'; +import type { UpdateQueue } from 'ReactFiberUpdateQueue'; + +var { LowPriority } = require('ReactPriorityLevel'); +var { + createUpdateQueue, + addToQueue, + addCallbackToQueue, +} = require('ReactFiberUpdateQueue'); +var ReactInstanceMap = require('ReactInstanceMap'); + +module.exports = function(scheduleUpdate : (fiber: Fiber, priorityLevel : PriorityLevel) => void) { + + function scheduleUpdateQueue(fiber: Fiber, updateQueue: UpdateQueue, priorityLevel : PriorityLevel) { + fiber.updateQueue = updateQueue; + // Schedule update on the alternate as well, since we don't know which tree + // is current. + if (fiber.alternate) { + fiber.alternate.updateQueue = updateQueue; + } + scheduleUpdate(fiber, priorityLevel); + } + + // Class component state updater + const updater = { + enqueueSetState(instance, partialState) { + const fiber = ReactInstanceMap.get(instance); + const updateQueue = fiber.updateQueue ? + addToQueue(fiber.updateQueue, partialState) : + createUpdateQueue(partialState); + scheduleUpdateQueue(fiber, updateQueue, LowPriority); + }, + enqueueReplaceState(instance, state) { + const fiber = ReactInstanceMap.get(instance); + const updateQueue = createUpdateQueue(state); + updateQueue.isReplace = true; + scheduleUpdateQueue(fiber, updateQueue, LowPriority); + }, + enqueueForceUpdate(instance) { + const fiber = ReactInstanceMap.get(instance); + const updateQueue = fiber.updateQueue || createUpdateQueue(null); + updateQueue.isForced = true; + scheduleUpdateQueue(fiber, updateQueue, LowPriority); + }, + enqueueCallback(instance, callback) { + const fiber = ReactInstanceMap.get(instance); + let updateQueue = fiber.updateQueue ? + fiber.updateQueue : + createUpdateQueue(null); + addCallbackToQueue(updateQueue, callback); + fiber.updateQueue = updateQueue; + if (fiber.alternate) { + fiber.alternate.updateQueue = updateQueue; + } + }, + }; + + function mount(workInProgress : Fiber, instance : any) { + const state = instance.state || null; + // The initial state must be added to the update queue in case + // setState is called before the initial render. + if (state !== null) { + workInProgress.updateQueue = createUpdateQueue(state); + } + // The instance needs access to the fiber so that it can schedule updates + ReactInstanceMap.set(instance, workInProgress); + instance.updater = updater; + } + + return { + mount, + }; + +}; diff --git a/src/renderers/shared/fiber/ReactFiberScheduler.js b/src/renderers/shared/fiber/ReactFiberScheduler.js index a1e0382ef77d..1118e65b4485 100644 --- a/src/renderers/shared/fiber/ReactFiberScheduler.js +++ b/src/renderers/shared/fiber/ReactFiberScheduler.js @@ -40,19 +40,11 @@ var { var timeHeuristicForUnitOfWork = 1; -export type Scheduler = { - scheduleDeferredWork: (root : FiberRoot, priority : PriorityLevel) => void -}; - module.exports = function(config : HostConfig) { // Use a closure to circumvent the circular dependency between the scheduler // and ReactFiberBeginWork. Don't know if there's a better way to do this. - let scheduler; - function getScheduler(): Scheduler { - return scheduler; - } - const { beginWork } = ReactFiberBeginWork(config, getScheduler); + const { beginWork } = ReactFiberBeginWork(config, scheduleUpdate); const { completeWork } = ReactFiberCompleteWork(config); const { commitInsertion, commitDeletion, commitWork, commitLifeCycles } = ReactFiberCommitWork(config); @@ -395,6 +387,31 @@ module.exports = function(config : HostConfig) { scheduleAnimationWork(root, defaultPriority); } + function scheduleUpdate(fiber: Fiber, priorityLevel : PriorityLevel): void { + while (true) { + if (fiber.pendingWorkPriority === NoWork || + fiber.pendingWorkPriority >= priorityLevel) { + fiber.pendingWorkPriority = priorityLevel; + } + if (fiber.alternate) { + if (fiber.alternate.pendingWorkPriority === NoWork || + fiber.alternate.pendingWorkPriority >= priorityLevel) { + fiber.alternate.pendingWorkPriority = priorityLevel; + } + } + // Duck type root + if (fiber.stateNode && fiber.stateNode.containerInfo) { + const root : FiberRoot = (fiber.stateNode : any); + scheduleDeferredWork(root, priorityLevel); + return; + } + if (!fiber.return) { + throw new Error('No root!'); + } + fiber = fiber.return; + } + } + function performWithPriority(priorityLevel : PriorityLevel, fn : Function) { const previousDefaultPriority = defaultPriority; defaultPriority = priorityLevel; @@ -405,10 +422,9 @@ module.exports = function(config : HostConfig) { } } - scheduler = { + return { scheduleWork: scheduleWork, scheduleDeferredWork: scheduleDeferredWork, performWithPriority: performWithPriority, }; - return scheduler; }; diff --git a/src/renderers/shared/fiber/ReactFiberUpdateQueue.js b/src/renderers/shared/fiber/ReactFiberUpdateQueue.js index aab252b65584..d352dd82c5f6 100644 --- a/src/renderers/shared/fiber/ReactFiberUpdateQueue.js +++ b/src/renderers/shared/fiber/ReactFiberUpdateQueue.js @@ -39,7 +39,7 @@ exports.createUpdateQueue = function(partialState : mixed) : UpdateQueue { return queue; }; -exports.addToQueue = function(queue : UpdateQueue, partialState : mixed) : UpdateQueue { +function addToQueue(queue : UpdateQueue, partialState : mixed) : UpdateQueue { const node = { partialState, callback: null, @@ -49,12 +49,14 @@ exports.addToQueue = function(queue : UpdateQueue, partialState : mixed) : Updat queue.tail.next = node; queue.tail = node; return queue; -}; +} + +exports.addToQueue = addToQueue; exports.addCallbackToQueue = function(queue : UpdateQueue, callback: Function) : UpdateQueue { if (queue.tail.callback) { // If the tail already as a callback, add an empty node to queue - exports.addToQueue(queue, null); + addToQueue(queue, null); } queue.tail.callback = callback; return queue; From fa1356ebe70e0c79b60f7469cbf6c52992979e28 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Tue, 11 Oct 2016 11:13:42 -0700 Subject: [PATCH 2/3] No duck typing on the root The root is always a host container. --- .../shared/fiber/ReactFiberScheduler.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/renderers/shared/fiber/ReactFiberScheduler.js b/src/renderers/shared/fiber/ReactFiberScheduler.js index 1118e65b4485..99c8682a17eb 100644 --- a/src/renderers/shared/fiber/ReactFiberScheduler.js +++ b/src/renderers/shared/fiber/ReactFiberScheduler.js @@ -38,6 +38,10 @@ var { Deletion, } = require('ReactTypeOfSideEffect'); +var { + HostContainer, +} = require('ReactTypeOfWork'); + var timeHeuristicForUnitOfWork = 1; module.exports = function(config : HostConfig) { @@ -399,14 +403,14 @@ module.exports = function(config : HostConfig) { fiber.alternate.pendingWorkPriority = priorityLevel; } } - // Duck type root - if (fiber.stateNode && fiber.stateNode.containerInfo) { - const root : FiberRoot = (fiber.stateNode : any); - scheduleDeferredWork(root, priorityLevel); - return; - } if (!fiber.return) { - throw new Error('No root!'); + if (fiber.tag === HostContainer) { + const root : FiberRoot = (fiber.stateNode : any); + scheduleDeferredWork(root, priorityLevel); + return; + } else { + throw new Error('Invalid root'); + } } fiber = fiber.return; } From c50502c55ade8b013b4765888e942512dedafae1 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Tue, 11 Oct 2016 15:52:11 -0700 Subject: [PATCH 3/3] Use memoizedState in componentDidUpdate We forgot to clone this value so it didn't work before. This is covered by existing tests in ReactDOMProduction. --- src/renderers/shared/fiber/ReactFiber.js | 1 + src/renderers/shared/fiber/ReactFiberCommitWork.js | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/renderers/shared/fiber/ReactFiber.js b/src/renderers/shared/fiber/ReactFiber.js index 6c1d557325aa..0eee40f1b31c 100644 --- a/src/renderers/shared/fiber/ReactFiber.js +++ b/src/renderers/shared/fiber/ReactFiber.js @@ -253,6 +253,7 @@ exports.cloneFiber = function(fiber : Fiber, priorityLevel : PriorityLevel) : Fi alt.pendingWorkPriority = priorityLevel; alt.memoizedProps = fiber.memoizedProps; + alt.memoizedState = fiber.memoizedState; alt.output = fiber.output; return alt; diff --git a/src/renderers/shared/fiber/ReactFiberCommitWork.js b/src/renderers/shared/fiber/ReactFiberCommitWork.js index 837f12d00c22..a972dc56230f 100644 --- a/src/renderers/shared/fiber/ReactFiberCommitWork.js +++ b/src/renderers/shared/fiber/ReactFiberCommitWork.js @@ -289,9 +289,7 @@ module.exports = function(config : HostConfig) { } else { if (typeof instance.componentDidUpdate === 'function') { const prevProps = current.memoizedProps; - // TODO: This is the new state. We don't currently have the previous - // state anymore. - const prevState = instance.state || null; + const prevState = current.memoizedState; instance.componentDidUpdate(prevProps, prevState); } }