diff --git a/src/renderers/shared/fiber/ReactFiber.js b/src/renderers/shared/fiber/ReactFiber.js index 48363ac2a0..b4dbb18880 100644 --- a/src/renderers/shared/fiber/ReactFiber.js +++ b/src/renderers/shared/fiber/ReactFiber.js @@ -82,6 +82,16 @@ export type Fiber = Instance & { // if this returns multiple values. Such as a fragment. output: any, // This type will be more specific once we overload the tag. + // Singly linked list fast path to the next fiber with side-effects. + nextEffect: ?Fiber, + + // The first and last fiber with side-effect within this subtree. This allows + // us to reuse a slice of the linked list when we reuse the work done within + // this fiber. + firstEffect: ?Fiber, + lastEffect: ?Fiber, + + // This will be used to quickly determine if a subtree has no pending changes. pendingWorkPriority: PriorityLevel, @@ -129,6 +139,10 @@ var createFiber = function(tag : TypeOfWork, key : null | string) : Fiber { memoizedProps: null, output: null, + nextEffect: null, + firstEffect: null, + lastEffect: null, + pendingWorkPriority: NoWork, hasWorkInProgress: false, @@ -157,6 +171,13 @@ exports.cloneFiber = function(fiber : Fiber, priorityLevel : PriorityLevel) : Fi alt.ref = alt.ref; alt.pendingProps = fiber.pendingProps; alt.pendingWorkPriority = priorityLevel; + + // Whenever we clone, we do so to get a new work in progress. + // This ensures that we've reset these in the new tree. + alt.nextEffect = null; + alt.firstEffect = null; + alt.lastEffect = null; + return alt; } diff --git a/src/renderers/shared/fiber/ReactFiberCommitWork.js b/src/renderers/shared/fiber/ReactFiberCommitWork.js new file mode 100644 index 0000000000..f4cce35f0c --- /dev/null +++ b/src/renderers/shared/fiber/ReactFiberCommitWork.js @@ -0,0 +1,38 @@ +/** + * 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 ReactFiberCommitWork + * @flow + */ + +'use strict'; + +import type { Fiber } from 'ReactFiber'; + +var ReactTypeOfWork = require('ReactTypeOfWork'); +var { + ClassComponent, + HostContainer, + HostComponent, +} = ReactTypeOfWork; + +exports.commitWork = function(finishedWork : Fiber) : void { + switch (finishedWork.tag) { + case ClassComponent: + // TODO: Fire componentDidMount/componentDidUpdate, update refs + return; + case HostContainer: + // TODO: Attach children to root container. + return; + case HostComponent: + console.log('commit updates to host component', finishedWork.type); + return; + default: + throw new Error('This unit of work tag should not have side-effects.'); + } +}; diff --git a/src/renderers/shared/fiber/ReactFiberCompleteWork.js b/src/renderers/shared/fiber/ReactFiberCompleteWork.js index d816510b40..53cc75533c 100644 --- a/src/renderers/shared/fiber/ReactFiberCompleteWork.js +++ b/src/renderers/shared/fiber/ReactFiberCompleteWork.js @@ -30,6 +30,16 @@ var { YieldComponent, } = ReactTypeOfWork; +function markForPostEffect(workInProgress : Fiber) { + // Schedule a side-effect on this fiber, after the children's side-effects. + if (workInProgress.lastEffect) { + workInProgress.lastEffect.nextEffect = workInProgress; + } else { + workInProgress.firstEffect = workInProgress; + } + workInProgress.lastEffect = workInProgress; +} + function transferOutput(child : ?Fiber, returnFiber : Fiber) { // If we have a single result, we just pass that through as the output to // avoid unnecessary traversal. When we have multiple output, we just pass @@ -108,6 +118,11 @@ exports.completeWork = function(current : ?Fiber, workInProgress : Fiber) : ?Fib return null; case HostComponent: transferOutput(workInProgress.child, workInProgress); + if (workInProgress.alternate) { + // If we have an alternate, that means this is an update and we need to + // schedule a side-effect to do the updates. + markForPostEffect(workInProgress); + } console.log('/host component', workInProgress.type); return null; case CoroutineComponent: diff --git a/src/renderers/shared/fiber/ReactFiberScheduler.js b/src/renderers/shared/fiber/ReactFiberScheduler.js index 4b6da086bf..50cf30eb5d 100644 --- a/src/renderers/shared/fiber/ReactFiberScheduler.js +++ b/src/renderers/shared/fiber/ReactFiberScheduler.js @@ -19,6 +19,7 @@ import type { HostConfig } from 'ReactFiberReconciler'; var { cloneFiber } = require('ReactFiber'); var { beginWork } = require('ReactFiberBeginWork'); var { completeWork } = require('ReactFiberCompleteWork'); +var { commitWork } = require('ReactFiberCommitWork'); var { findNextUnitOfWorkAtPriority } = require('ReactFiberPendingWork'); var { @@ -78,6 +79,22 @@ module.exports = function(config : HostConfig) { return null; } + function commitAllWork(finishedWork : Fiber) { + // Commit all the side-effects within a tree. + // TODO: Error handling. + let effectfulFiber = finishedWork.firstEffect; + while (effectfulFiber) { + commitWork(effectfulFiber); + const next = effectfulFiber.nextEffect; + // Ensure that we clean these up so that we don't accidentally keep them. + // I'm not actually sure this matters because we can't reset firstEffect + // and lastEffect since they're on every node, not just the effectful + // ones. So we have to clean everything as we reuse nodes anyway. + effectfulFiber.nextEffect = null; + effectfulFiber = next; + } + } + function completeUnitOfWork(workInProgress : Fiber) : ?Fiber { while (true) { // The current, flushed, state of this fiber is the alternate. @@ -96,11 +113,25 @@ module.exports = function(config : HostConfig) { const returnFiber = workInProgress.return; - // Ensure that remaining work priority bubbles up. - if (returnFiber && workInProgress.pendingWorkPriority !== NoWork && - (returnFiber.pendingWorkPriority === NoWork || - returnFiber.pendingWorkPriority > workInProgress.pendingWorkPriority)) { - returnFiber.pendingWorkPriority = workInProgress.pendingWorkPriority; + if (returnFiber) { + // Ensure that remaining work priority bubbles up. + if (workInProgress.pendingWorkPriority !== NoWork && + (returnFiber.pendingWorkPriority === NoWork || + returnFiber.pendingWorkPriority > workInProgress.pendingWorkPriority)) { + returnFiber.pendingWorkPriority = workInProgress.pendingWorkPriority; + } + // Ensure that the first and last effect of the parent corresponds + // to the children's first and last effect. This probably relies on + // children completing in order. + if (!returnFiber.firstEffect) { + returnFiber.firstEffect = workInProgress.firstEffect; + } + if (workInProgress.lastEffect) { + if (returnFiber.lastEffect) { + returnFiber.lastEffect.nextEffect = workInProgress.firstEffect; + } + returnFiber.lastEffect = workInProgress.lastEffect; + } } if (next) { @@ -121,6 +152,7 @@ module.exports = function(config : HostConfig) { // also ensures that work scheduled during reconciliation gets deferred. // const hasMoreWork = workInProgress.pendingWorkPriority !== NoWork; console.log('----- COMPLETED with remaining work:', workInProgress.pendingWorkPriority); + commitAllWork(workInProgress); const nextWork = findNextUnitOfWork(); // if (!nextWork && hasMoreWork) { // TODO: This can happen when some deep work completes and we don't