diff --git a/src/renderers/shared/fiber/ReactFiberBeginWork.js b/src/renderers/shared/fiber/ReactFiberBeginWork.js index b75fe2e67b..c199c8c921 100644 --- a/src/renderers/shared/fiber/ReactFiberBeginWork.js +++ b/src/renderers/shared/fiber/ReactFiberBeginWork.js @@ -40,9 +40,6 @@ var { NoWork, OffscreenPriority, } = require('ReactPriorityLevel'); -var { - mergeUpdateQueue, -} = require('ReactFiberUpdateQueue'); var { Placement, } = require('ReactTypeOfSideEffect'); @@ -51,7 +48,11 @@ var ReactFiberClassComponent = require('ReactFiberClassComponent'); module.exports = function(config : HostConfig, scheduleUpdate : (fiber: Fiber, priorityLevel : PriorityLevel) => void) { const { - mount, + adoptClassInstance, + constructClassInstance, + mountClassInstance, + resumeMountClassInstance, + updateClassInstance, } = ReactFiberClassComponent(scheduleUpdate); function markChildAsProgressed(current, workInProgress, priorityLevel) { @@ -156,54 +157,27 @@ module.exports = function(config : HostConfig, s } 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 - // memoized props. - var props = workInProgress.pendingProps; - if (!props) { - // If there isn't any new props, then we'll reuse the memoized props. - // This could be from already completed work. - props = workInProgress.memoizedProps; - if (!props) { - throw new Error('There should always be pending or memoized props.'); + let shouldUpdate; + if (!current) { + if (!workInProgress.stateNode) { + // In the initial pass we might need to construct the instance. + constructClassInstance(workInProgress); + mountClassInstance(workInProgress); + shouldUpdate = true; + } else { + // In a resume, we'll already have an instance we can reuse. + shouldUpdate = resumeMountClassInstance(workInProgress); } - } - - // Compute the state using the memoized state and the update queue. - var updateQueue = workInProgress.updateQueue; - var previousState = workInProgress.memoizedState; - var state; - if (updateQueue) { - state = mergeUpdateQueue(updateQueue, previousState, props); } else { - state = previousState; + shouldUpdate = updateClassInstance(current, workInProgress); } - - var instance = workInProgress.stateNode; - if (!instance) { - var ctor = workInProgress.type; - workInProgress.stateNode = instance = new ctor(props); - mount(workInProgress, instance); - state = instance.state || null; - } else if (typeof instance.shouldComponentUpdate === 'function' && - !(updateQueue && updateQueue.isForced)) { - if (workInProgress.memoizedProps !== null) { - // Reset the props, in case this is a ping-pong case rather than a - // completed update case. For the completed update case, the instance - // props will already be the memoizedProps. - instance.props = workInProgress.memoizedProps; - instance.state = workInProgress.memoizedState; - if (!instance.shouldComponentUpdate(props, state)) { - return bailoutOnAlreadyFinishedWork(current, workInProgress); - } - } + if (!shouldUpdate) { + return bailoutOnAlreadyFinishedWork(current, workInProgress); } - - instance.props = props; - instance.state = state; - var nextChildren = instance.render(); + // Rerender + const instance = workInProgress.stateNode; + const nextChildren = instance.render(); reconcileChildren(current, workInProgress, nextChildren); - return workInProgress.child; } @@ -258,22 +232,21 @@ module.exports = function(config : HostConfig, s } function mountIndeterminateComponent(current, workInProgress) { + if (current) { + throw new Error('An indeterminate component should never have mounted.'); + } var fn = workInProgress.type; var props = workInProgress.pendingProps; var value = fn(props); if (typeof value === 'object' && value && typeof value.render === 'function') { // Proceed under the assumption that this is a class instance workInProgress.tag = ClassComponent; - if (current) { - current.tag = ClassComponent; - } + adoptClassInstance(workInProgress, value); + mountClassInstance(workInProgress); value = value.render(); } else { // Proceed under the assumption that this is a functional component workInProgress.tag = FunctionalComponent; - if (current) { - current.tag = FunctionalComponent; - } } reconcileChildren(current, workInProgress, value); return workInProgress.child; diff --git a/src/renderers/shared/fiber/ReactFiberClassComponent.js b/src/renderers/shared/fiber/ReactFiberClassComponent.js index 6110057607..33a6482f2d 100644 --- a/src/renderers/shared/fiber/ReactFiberClassComponent.js +++ b/src/renderers/shared/fiber/ReactFiberClassComponent.js @@ -21,6 +21,7 @@ var { createUpdateQueue, addToQueue, addCallbackToQueue, + mergeUpdateQueue, } = require('ReactFiberUpdateQueue'); var ReactInstanceMap = require('ReactInstanceMap'); @@ -70,20 +71,155 @@ module.exports = function(scheduleUpdate : (fiber: Fiber, priorityLevel : Priori }, }; - 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); - } + function adoptClassInstance(workInProgress : Fiber, instance : any) : void { + instance.updater = updater; + workInProgress.stateNode = instance; // The instance needs access to the fiber so that it can schedule updates ReactInstanceMap.set(instance, workInProgress); - instance.updater = updater; + } + + function constructClassInstance(workInProgress : Fiber) : any { + const ctor = workInProgress.type; + const props = workInProgress.pendingProps; + const instance = new ctor(props); + adoptClassInstance(workInProgress, instance); + return instance; + } + + // Invokes the mount life-cycles on a previously never rendered instance. + function mountClassInstance(workInProgress : Fiber) : void { + const instance = workInProgress.stateNode; + + const state = instance.state || null; + + // 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 + // memoized props. + let props = workInProgress.pendingProps; + if (!props) { + throw new Error('There must be pending props for an initial mount.'); + } + + instance.props = props; + instance.state = state; + + if (typeof instance.componentWillMount === 'function') { + instance.componentWillMount(); + // If we had additional state updates during this life-cycle, let's + // process them now. + const updateQueue = workInProgress.updateQueue; + if (updateQueue) { + instance.state = mergeUpdateQueue(updateQueue, state, props); + } + } + } + + // Called on a preexisting class instance. Returns false if a resumed render + // could be reused. + function resumeMountClassInstance(workInProgress : Fiber) : boolean { + const instance = workInProgress.stateNode; + let newState = workInProgress.memoizedState; + let newProps = workInProgress.pendingProps; + if (!newProps) { + // If there isn't any new props, then we'll reuse the memoized props. + // This could be from already completed work. + newProps = workInProgress.memoizedProps; + if (!newProps) { + throw new Error('There should always be pending or memoized props.'); + } + } + + // TODO: Should we deal with a setState that happened after the last + // componentWillMount and before this componentWillMount? Probably + // unsupported anyway. + + const updateQueue = workInProgress.updateQueue; + + // If this completed, we might be able to just reuse this instance. + if (typeof instance.shouldComponentUpdate === 'function' && + !(updateQueue && updateQueue.isForced) && + workInProgress.memoizedProps !== null && + !instance.shouldComponentUpdate(newProps, newState)) { + return false; + } + + // If we didn't bail out we need to construct a new instance. We don't + // want to reuse one that failed to fully mount. + const newInstance = constructClassInstance(workInProgress); + newInstance.props = newProps; + newInstance.state = newState = newInstance.state || null; + + if (typeof newInstance.componentWillMount === 'function') { + newInstance.componentWillMount(); + // If we had additional state updates during this life-cycle, let's + // process them now. + const newUpdateQueue = workInProgress.updateQueue; + if (newUpdateQueue) { + instance.state = mergeUpdateQueue(newUpdateQueue, newState, newProps); + } + } + return true; + } + + // Invokes the update life-cycles and returns false if it shouldn't rerender. + function updateClassInstance(current : Fiber, workInProgress : Fiber) : boolean { + const instance = workInProgress.stateNode; + + const oldProps = current.memoizedProps; + let newProps = workInProgress.pendingProps; + if (!newProps) { + // If there aren't any new props, then we'll reuse the memoized props. + // This could be from already completed work. + newProps = workInProgress.memoizedProps; + if (!newProps) { + throw new Error('There should always be pending or memoized props.'); + } + } + + // Note: During these life-cycles, instance.props/instance.state are what + // ever the previously attempted to render - not the "current". However, + // during componentDidUpdate we pass the "current" props. + + if (oldProps !== newProps) { + if (typeof instance.componentWillReceiveProps === 'function') { + instance.componentWillReceiveProps(newProps); + } + } + + // Compute the next state using the memoized state and the update queue. + const updateQueue = workInProgress.updateQueue; + const previousState = workInProgress.memoizedState; + // TODO: Previous state can be null. + let newState; + if (updateQueue) { + newState = mergeUpdateQueue(updateQueue, previousState, newProps); + } else { + newState = previousState; + } + + if (typeof instance.shouldComponentUpdate === 'function' && + !(updateQueue && updateQueue.isForced) && + workInProgress.memoizedProps !== null && + !instance.shouldComponentUpdate(newProps, newState)) { + // TODO: Should this get the new props/state updated regardless? + return false; + } + + if (typeof instance.componentWillUpdate === 'function') { + instance.componentWillUpdate(newProps, newState); + } + + instance.props = newProps; + instance.state = newState; + return true; } return { - mount, + adoptClassInstance, + constructClassInstance, + mountClassInstance, + resumeMountClassInstance, + updateClassInstance, }; };