mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Fiber side-effects
This adds tracking of side-effects that gets scheduled during an update. As the tree gets reconciled, the side-effectful fibers are linked together in an ordered singly linked list. That way we can walk the linked list to commit only the work that needs to be synchronous - quickly. We also store first and last nodes within a fiber. That way when we reuse an already processed subtree, we can reuse that subset of the linked list.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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.');
|
||||
}
|
||||
};
|
||||
@@ -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:
|
||||
|
||||
@@ -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<T, P, I>(config : HostConfig<T, P, I>) {
|
||||
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<T, P, I>(config : HostConfig<T, P, I>) {
|
||||
|
||||
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<T, P, I>(config : HostConfig<T, P, I>) {
|
||||
// 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
|
||||
|
||||
Reference in New Issue
Block a user