From 56a6f52960444babe9e1af44708f5b01e2dd1947 Mon Sep 17 00:00:00 2001 From: Edvin Erikson Date: Sun, 6 Nov 2016 14:00:34 +0100 Subject: [PATCH 1/3] approach one --- src/renderers/shared/fiber/ReactFiber.js | 4 + .../shared/fiber/ReactFiberClassComponent.js | 115 ++++++++++++++++-- 2 files changed, 108 insertions(+), 11 deletions(-) diff --git a/src/renderers/shared/fiber/ReactFiber.js b/src/renderers/shared/fiber/ReactFiber.js index 739f6636607fe..ebb2271f16293 100644 --- a/src/renderers/shared/fiber/ReactFiber.js +++ b/src/renderers/shared/fiber/ReactFiber.js @@ -89,6 +89,8 @@ export type Fiber = { pendingProps: any, // This type will be more specific once we overload the tag. // TODO: I think that there is a way to merge pendingProps and memoizedProps. memoizedProps: any, // The props used to create the output. + // Context + childContext: any, // A queue of local state updates. updateQueue: ?UpdateQueue, // The state used to create the output. This is a full state object. @@ -180,6 +182,7 @@ var createFiber = function(tag : TypeOfWork, key : null | string) : Fiber { ref: null, pendingProps: null, + childContext: null, memoizedProps: null, updateQueue: null, memoizedState: null, @@ -251,6 +254,7 @@ exports.cloneFiber = function(fiber : Fiber, priorityLevel : PriorityLevel) : Fi // pendingProps is here for symmetry but is unnecessary in practice for now. // TODO: Pass in the new pendingProps as an argument maybe? alt.pendingProps = fiber.pendingProps; + alt.childContext = fiber.childContext; alt.updateQueue = fiber.updateQueue; alt.callbackList = fiber.callbackList; alt.pendingWorkPriority = priorityLevel; diff --git a/src/renderers/shared/fiber/ReactFiberClassComponent.js b/src/renderers/shared/fiber/ReactFiberClassComponent.js index 5d9be3005e344..d6d9fd160b074 100644 --- a/src/renderers/shared/fiber/ReactFiberClassComponent.js +++ b/src/renderers/shared/fiber/ReactFiberClassComponent.js @@ -27,6 +27,11 @@ var shallowEqual = require('shallowEqual'); var warning = require('warning'); var invariant = require('invariant'); +let checkReactTypeSpec; + +if (__DEV__) { + checkReactTypeSpec = require('checkReactTypeSpec'); +} const isArray = Array.isArray; @@ -74,7 +79,7 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) { }, }; - function checkShouldComponentUpdate(workInProgress, oldProps, newProps, newState) { + function checkShouldComponentUpdate(workInProgress, oldProps, newProps, newState, newContext) { const updateQueue = workInProgress.updateQueue; if (oldProps === null || (updateQueue && updateQueue.isForced)) { return true; @@ -82,14 +87,15 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) { const instance = workInProgress.stateNode; if (typeof instance.shouldComponentUpdate === 'function') { - return instance.shouldComponentUpdate(newProps, newState); + return instance.shouldComponentUpdate(newProps, newState, newContext); } const type = workInProgress.type; if (type.prototype && type.prototype.isPureReactComponent) { return ( !shallowEqual(oldProps, newProps) || - !shallowEqual(instance.state, newState) + !shallowEqual(instance.state, newState) || + !shallowEqual(instance.context, newContext) ); } @@ -195,10 +201,91 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) { ReactInstanceMap.set(instance, workInProgress); } + function checkContextTypes(workInProgress : Fiber, typeSpecs: {}, values: {}, location: string) { + checkReactTypeSpec( + typeSpecs, + values, + location, + getName(workInProgress, workInProgress.stateNode), + null, + null + ); + } + + function maskContext(context: {}, contextTypes: {}): any { + if (!contextTypes) { + return {}; + } + + const maskedContext = {}; + for (let contextName in contextTypes) { + maskedContext[contextName] = context[contextName]; + } + + return maskedContext; + } + + function processChildContext(workInProgress : Fiber, instance : any) { + const Component = workInProgress.type; + const currentContext = workInProgress.return && workInProgress.return.childContext; + let childContext; + if (instance.getChildContext) { + if (__DEV__) { + // ReactFiberInstrumentation.debugTool.onBeginProcessingChildContext(); + try { + childContext = instance.getChildContext(); + } finally { + // ReactFiberInstrumentation.debugTool.onEndProcessingChildContext(); + } + } else { + childContext = instance.getChildContext(); + } + } + + if (childContext) { + invariant( + typeof Component.childContextTypes === 'object', + '%s.getChildContext(): childContextTypes must be defined in order to ' + + 'use getChildContext().', + getName(workInProgress, instance) + ); + + if (__DEV__) { + checkContextTypes(workInProgress, Component.childContextTypes, childContext, 'childContext'); + } + + for (let name in childContext) { + invariant( + name in Component.childContextTypes, + '%s.getChildContext(): key "%s" is not defined in childContextTypes.', + getName(workInProgress, instance), + name + ); + } + return Object.assign({}, currentContext, childContext); + } + return currentContext; + } + + function processContext(workInProgress : Fiber) { + const Component = workInProgress.type; + const contextTypes = Component.contextTypes; + const context = workInProgress.return && workInProgress.return.childContext; + const maskedContext = maskContext(context, contextTypes); + if (__DEV__) { + if (contextTypes) { + checkContextTypes(workInProgress, contextTypes, maskedContext, 'context'); + } + } + return maskedContext; + } + function constructClassInstance(workInProgress : Fiber) : any { const ctor = workInProgress.type; const props = workInProgress.pendingProps; - const instance = new ctor(props); + const context = processContext(workInProgress); + const instance = new ctor(props, context); + checkClassInstance(workInProgress, instance); adoptClassInstance(workInProgress, instance); return instance; @@ -217,6 +304,9 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) { instance.props = props; instance.state = state; + instance.context = processContext(workInProgress); + + workInProgress.childContext = processChildContext(workInProgress, instance); if (typeof instance.componentWillMount === 'function') { instance.componentWillMount(); @@ -232,6 +322,7 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) { // Called on a preexisting class instance. Returns false if a resumed render // could be reused. function resumeMountClassInstance(workInProgress : Fiber) : boolean { + const newContext = processContext(workInProgress); let newState = workInProgress.memoizedState; let newProps = workInProgress.pendingProps; if (!newProps) { @@ -242,7 +333,6 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) { 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. @@ -251,7 +341,8 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) { workInProgress, workInProgress.memoizedProps, newProps, - newState + newState, + newContext )) { return false; } @@ -261,7 +352,7 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) { const newInstance = constructClassInstance(workInProgress); newInstance.props = newProps; newInstance.state = newState = newInstance.state || null; - + newInstance.context = newContext; if (typeof newInstance.componentWillMount === 'function') { newInstance.componentWillMount(); } @@ -278,7 +369,7 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) { // 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 newContext = processContext(workInProgress); const oldProps = workInProgress.memoizedProps || current.memoizedProps; let newProps = workInProgress.pendingProps; if (!newProps) { @@ -296,7 +387,7 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) { if (oldProps !== newProps) { if (typeof instance.componentWillReceiveProps === 'function') { - instance.componentWillReceiveProps(newProps); + instance.componentWillReceiveProps(newProps, newContext); } } @@ -325,18 +416,20 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) { workInProgress, oldProps, newProps, - newState + newState, + newContext )) { // TODO: Should this get the new props/state updated regardless? return false; } if (typeof instance.componentWillUpdate === 'function') { - instance.componentWillUpdate(newProps, newState); + instance.componentWillUpdate(newProps, newState, newContext); } instance.props = newProps; instance.state = newState; + instance.context = newContext; return true; } From 0d1048d6496def6ab85bca48f877876f0d4cf914 Mon Sep 17 00:00:00 2001 From: Edvin Erikson Date: Sun, 6 Nov 2016 17:24:05 +0100 Subject: [PATCH 2/3] approach two --- src/renderers/shared/fiber/ReactFiber.js | 6 ++- .../shared/fiber/ReactFiberClassComponent.js | 54 ++++++++++++------- .../shared/fiber/ReactFiberCompleteWork.js | 7 ++- 3 files changed, 46 insertions(+), 21 deletions(-) diff --git a/src/renderers/shared/fiber/ReactFiber.js b/src/renderers/shared/fiber/ReactFiber.js index ebb2271f16293..075490d715a80 100644 --- a/src/renderers/shared/fiber/ReactFiber.js +++ b/src/renderers/shared/fiber/ReactFiber.js @@ -182,10 +182,11 @@ var createFiber = function(tag : TypeOfWork, key : null | string) : Fiber { ref: null, pendingProps: null, - childContext: null, memoizedProps: null, updateQueue: null, memoizedState: null, + memoizedContext: null, + memoizedChildContext: null, callbackList: null, output: null, @@ -254,13 +255,14 @@ exports.cloneFiber = function(fiber : Fiber, priorityLevel : PriorityLevel) : Fi // pendingProps is here for symmetry but is unnecessary in practice for now. // TODO: Pass in the new pendingProps as an argument maybe? alt.pendingProps = fiber.pendingProps; - alt.childContext = fiber.childContext; alt.updateQueue = fiber.updateQueue; alt.callbackList = fiber.callbackList; alt.pendingWorkPriority = priorityLevel; alt.memoizedProps = fiber.memoizedProps; alt.memoizedState = fiber.memoizedState; + alt.memoizedContext = fiber.memoizedContext; + alt.memoizedChildContext = fiber.memoizedChildContext; alt.output = fiber.output; return alt; diff --git a/src/renderers/shared/fiber/ReactFiberClassComponent.js b/src/renderers/shared/fiber/ReactFiberClassComponent.js index d6d9fd160b074..df2d0ef3b7112 100644 --- a/src/renderers/shared/fiber/ReactFiberClassComponent.js +++ b/src/renderers/shared/fiber/ReactFiberClassComponent.js @@ -227,7 +227,12 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) { function processChildContext(workInProgress : Fiber, instance : any) { const Component = workInProgress.type; - const currentContext = workInProgress.return && workInProgress.return.childContext; + const currentContext = ( + workInProgress.return && + workInProgress.return.stateNode && + workInProgress.return.stateNode._childContext + ); + let childContext; if (instance.getChildContext) { if (__DEV__) { @@ -270,8 +275,12 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) { function processContext(workInProgress : Fiber) { const Component = workInProgress.type; const contextTypes = Component.contextTypes; - const context = workInProgress.return && workInProgress.return.childContext; - const maskedContext = maskContext(context, contextTypes); + const currentContext = ( + workInProgress.return && + workInProgress.return.stateNode && + workInProgress.return.stateNode._childContext + ); + const maskedContext = maskContext(currentContext, contextTypes); if (__DEV__) { if (contextTypes) { checkContextTypes(workInProgress, contextTypes, maskedContext, 'context'); @@ -304,9 +313,8 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) { instance.props = props; instance.state = state; - instance.context = processContext(workInProgress); - - workInProgress.childContext = processChildContext(workInProgress, instance); + instance.context = instance.context || processContext(workInProgress); + instance._childContext = instance.childContext || processChildContext(workInProgress, instance); if (typeof instance.componentWillMount === 'function') { instance.componentWillMount(); @@ -315,6 +323,8 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) { const updateQueue = workInProgress.updateQueue; if (updateQueue) { instance.state = mergeUpdateQueue(updateQueue, instance, state, props); + // instance.context = processContext(workInProgress); + instance._childContext = processChildContext(workInProgress, instance); } } } @@ -322,7 +332,8 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) { // Called on a preexisting class instance. Returns false if a resumed render // could be reused. function resumeMountClassInstance(workInProgress : Fiber) : boolean { - const newContext = processContext(workInProgress); + const newContext = workInProgress.memoizedContext; + const newChildContext = workInProgress.memoizedChildContext; let newState = workInProgress.memoizedState; let newProps = workInProgress.pendingProps; if (!newProps) { @@ -353,6 +364,7 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) { newInstance.props = newProps; newInstance.state = newState = newInstance.state || null; newInstance.context = newContext; + newInstance._childContext = newChildContext; if (typeof newInstance.componentWillMount === 'function') { newInstance.componentWillMount(); } @@ -362,6 +374,8 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) { const newUpdateQueue = workInProgress.updateQueue; if (newUpdateQueue) { newInstance.state = mergeUpdateQueue(newUpdateQueue, newInstance, newState, newProps); + // newInstance.context = processContext(workInProgress); + newInstance._childContext = processChildContext(workInProgress, newInstance); } return true; } @@ -369,8 +383,9 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) { // 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 newContext = processContext(workInProgress); const oldProps = workInProgress.memoizedProps || current.memoizedProps; + const oldContext = workInProgress.memoizedContext || current.memoizedContext; + let newProps = workInProgress.pendingProps; if (!newProps) { // If there aren't any new props, then we'll reuse the memoized props. @@ -381,16 +396,6 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) { } } - // 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, newContext); - } - } - // Compute the next state using the memoized state and the update queue. const updateQueue = workInProgress.updateQueue; const previousState = workInProgress.memoizedState; @@ -406,8 +411,20 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) { newState = previousState; } + let newContext = processContext(workInProgress); + // 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 || oldContext !== newContext) { + if (typeof instance.componentWillReceiveProps === 'function') { + instance.componentWillReceiveProps(newProps, newContext); + } + } + if (oldProps === newProps && previousState === newState && + oldContext === newContext && updateQueue && !updateQueue.isForced) { return false; } @@ -430,6 +447,7 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) { instance.props = newProps; instance.state = newState; instance.context = newContext; + instance._childContext = processChildContext(workInProgress, instance); return true; } diff --git a/src/renderers/shared/fiber/ReactFiberCompleteWork.js b/src/renderers/shared/fiber/ReactFiberCompleteWork.js index b0eea7c87e25c..0d3932c9fac8f 100644 --- a/src/renderers/shared/fiber/ReactFiberCompleteWork.js +++ b/src/renderers/shared/fiber/ReactFiberCompleteWork.js @@ -129,13 +129,18 @@ module.exports = function(config : HostConfig) { // merged it and assigned it to the instance. Transfer it from there. // Also need to transfer the props, because pendingProps will be null // in the case of an update - const { state, props } = workInProgress.stateNode; + const { state, props, context, _childContext } = workInProgress.stateNode; const updateQueue = workInProgress.updateQueue; workInProgress.memoizedState = state; workInProgress.memoizedProps = props; + workInProgress.memoizedContext = context; + workInProgress.memoizedChildContext = _childContext; + if (current) { if (current.memoizedProps !== workInProgress.memoizedProps || current.memoizedState !== workInProgress.memoizedState || + current.memoizedContext !== workInProgress.memoizedContext || + current.memoizedChildContext !== workInProgress.memoizedChildContext || updateQueue && updateQueue.isForced) { markUpdate(workInProgress); } From 1919236edf468a1ee619129819d2d45af95cf967 Mon Sep 17 00:00:00 2001 From: Edvin Erikson Date: Sun, 6 Nov 2016 17:25:03 +0100 Subject: [PATCH 3/3] temporary context example --- examples/fiber/index.html | 46 +++++++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/examples/fiber/index.html b/examples/fiber/index.html index c90e7ce246ed1..8e5b7b58b0362 100644 --- a/examples/fiber/index.html +++ b/examples/fiber/index.html @@ -20,6 +20,40 @@

Fiber Example