From 62d4561910aa6c8f07ddd0f40415b70e546b81dc Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Thu, 30 Jun 2016 00:22:59 -0700 Subject: [PATCH] Host environment side-effects This creates a new API for processing side-effects on the host environment. During initial reconciliation host instances are created during the time sliced periods. During updates there is an opportunity for the host to prepare something on the instance during the time slicing, and to determine whether there were any changes. The could be thrown away. At the commit phase, these changes are finally committed to the host instance. --- src/renderers/noop/ReactNoop.js | 28 +++++++++++++++++-- src/renderers/shared/fiber/ReactFiber.js | 2 +- .../shared/fiber/ReactFiberCommitWork.js | 14 +++++++++- .../shared/fiber/ReactFiberCompleteWork.js | 21 +++++++++++--- .../shared/fiber/ReactFiberReconciler.js | 15 ++++++---- 5 files changed, 66 insertions(+), 14 deletions(-) diff --git a/src/renderers/noop/ReactNoop.js b/src/renderers/noop/ReactNoop.js index 9214a1865b..4ffa0b6582 100644 --- a/src/renderers/noop/ReactNoop.js +++ b/src/renderers/noop/ReactNoop.js @@ -20,20 +20,44 @@ 'use strict'; import type { Fiber } from 'ReactFiber'; +import type { HostChildren } from 'ReactFiberReconciler'; var ReactFiberReconciler = require('ReactFiberReconciler'); var scheduledHighPriCallback = null; var scheduledLowPriCallback = null; +type Props = { }; +type Instance = { id: number }; + +var instanceCounter = 0; + var NoopRenderer = ReactFiberReconciler({ - createHostInstance() { - + createInstance(type : string, props : Props, children : HostChildren) : Instance { + console.log('Create instance #' + instanceCounter); + return { + id: instanceCounter++ + }; }, + + prepareUpdate(instance : Instance, oldProps : Props, newProps : Props, children : HostChildren) : boolean { + console.log('Prepare for update on #' + instance.id); + return true; + }, + + commitUpdate(instance : Instance, oldProps : Props, newProps : Props, children : HostChildren) : void { + console.log('Commit update on #' + instance.id); + }, + + deleteInstance(instance : Instance) : void { + console.log('Delete #' + instance.id); + }, + scheduleHighPriCallback(callback) { scheduledHighPriCallback = callback; }, + scheduleLowPriCallback(callback) { scheduledLowPriCallback = callback; }, diff --git a/src/renderers/shared/fiber/ReactFiber.js b/src/renderers/shared/fiber/ReactFiber.js index b4dbb18880..39683f001e 100644 --- a/src/renderers/shared/fiber/ReactFiber.js +++ b/src/renderers/shared/fiber/ReactFiber.js @@ -48,7 +48,7 @@ type Instance = { type: any, // The local state associated with this fiber. - stateNode: ?Object, + stateNode: any, // Conceptual aliases // parent : Instance -> return The parent happens to be the same as the diff --git a/src/renderers/shared/fiber/ReactFiberCommitWork.js b/src/renderers/shared/fiber/ReactFiberCommitWork.js index 161c2b3c8d..1f3e5e5a22 100644 --- a/src/renderers/shared/fiber/ReactFiberCommitWork.js +++ b/src/renderers/shared/fiber/ReactFiberCommitWork.js @@ -24,6 +24,8 @@ var { module.exports = function(config : HostConfig) { + const commitUpdate = config.commitUpdate; + function commitWork(finishedWork : Fiber) : void { switch (finishedWork.tag) { case ClassComponent: @@ -33,7 +35,17 @@ module.exports = function(config : HostConfig) { // TODO: Attach children to root container. return; case HostComponent: - console.log('commit updates to host component', finishedWork.type); + if (finishedWork.stateNode == null || !finishedWork.alternate) { + throw new Error('This should only be done during updates.'); + } + const children = finishedWork.output; + const newProps = finishedWork.memoizedProps; + // If we have an alternate, that means this is an update and we need to + // schedule a side-effect to do the updates. + const current = finishedWork.alternate; + const oldProps = current.memoizedProps; + const instance : I = finishedWork.stateNode; + commitUpdate(instance, oldProps, newProps, children); 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 50c6e6895f..6f4752ccc0 100644 --- a/src/renderers/shared/fiber/ReactFiberCompleteWork.js +++ b/src/renderers/shared/fiber/ReactFiberCompleteWork.js @@ -15,7 +15,6 @@ import type { ReactCoroutine } from 'ReactCoroutine'; import type { Fiber } from 'ReactFiber'; import type { HostConfig } from 'ReactFiberReconciler'; - import type { ReifiedYield } from 'ReactReifiedYield'; var ReactChildFiber = require('ReactChildFiber'); @@ -33,6 +32,9 @@ var { module.exports = function(config : HostConfig) { + const createInstance = config.createInstance; + const prepareUpdate = config.prepareUpdate; + function markForPostEffect(workInProgress : Fiber) { // Schedule a side-effect on this fiber, after the children's side-effects. if (workInProgress.lastEffect) { @@ -120,13 +122,24 @@ module.exports = function(config : HostConfig) { case HostContainer: return null; case HostComponent: + console.log('/host component', workInProgress.type); transferOutput(workInProgress.child, workInProgress); - if (workInProgress.alternate) { + const children = workInProgress.output; + const newProps = workInProgress.memoizedProps; + if (workInProgress.alternate && workInProgress.stateNode != null) { // 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); + const current = workInProgress.alternate; + const oldProps = current.memoizedProps; + const instance : I = workInProgress.stateNode; + if (prepareUpdate(instance, oldProps, newProps, children)) { + // This returns true if there was something to update. + markForPostEffect(workInProgress); + } + } else { + const instance = createInstance(workInProgress.type, newProps, children); + workInProgress.stateNode = instance; } - console.log('/host component', workInProgress.type); return null; case CoroutineComponent: console.log('/coroutine component', workInProgress.pendingProps.handler.name); diff --git a/src/renderers/shared/fiber/ReactFiberReconciler.js b/src/renderers/shared/fiber/ReactFiberReconciler.js index 3fde615cf0..28421314ab 100644 --- a/src/renderers/shared/fiber/ReactFiberReconciler.js +++ b/src/renderers/shared/fiber/ReactFiberReconciler.js @@ -22,18 +22,21 @@ var { LowPriority, } = require('ReactPriorityLevel'); -type ReactHostElement = { - type: T, - props: P -}; - type Deadline = { timeRemaining : () => number }; +type HostChildNode = { output: HostChildren, sibling: ?HostChildNode }; + +export type HostChildren = null | void | I | HostChildNode; + export type HostConfig = { - createHostInstance(element : ReactHostElement) : I, + createInstance(type : T, props : P, children : HostChildren) : I, + prepareUpdate(instance : I, oldProps : P, newProps : P, children : HostChildren) : bool, + commitUpdate(instance : I, oldProps : P, newProps : P, children : HostChildren) : void, + deleteInstance(instance : I) : void, + scheduleHighPriCallback(callback : () => void) : void, scheduleLowPriCallback(callback : (deadline : Deadline) => void) : void