mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Merge pull request #8272 from gaearon/fiber-context
[Fiber] Initial implementation of context
This commit is contained in:
@@ -19,8 +19,7 @@ src/addons/transitions/__tests__/ReactCSSTransitionGroup-test.js
|
||||
* should transition from false to one
|
||||
|
||||
src/isomorphic/classic/__tests__/ReactContextValidator-test.js
|
||||
* should filter out context not in contextTypes
|
||||
* should filter context properly in callbacks
|
||||
* should pass previous context to lifecycles
|
||||
|
||||
src/isomorphic/classic/class/__tests__/ReactBind-test.js
|
||||
* Holds reference to instance
|
||||
@@ -31,24 +30,9 @@ src/isomorphic/classic/class/__tests__/ReactBindOptout-test.js
|
||||
* works with mixins that have not opted out of autobinding
|
||||
* works with mixins that have opted out of autobinding
|
||||
|
||||
src/isomorphic/classic/class/__tests__/ReactClass-test.js
|
||||
* renders based on context getInitialState
|
||||
|
||||
src/isomorphic/classic/element/__tests__/ReactElementValidator-test.js
|
||||
* includes the owner name when passing null, undefined, boolean, or number
|
||||
|
||||
src/isomorphic/modern/class/__tests__/ReactCoffeeScriptClass-test.coffee
|
||||
* renders based on context in the constructor
|
||||
* supports this.context passed via getChildContext
|
||||
|
||||
src/isomorphic/modern/class/__tests__/ReactES6Class-test.js
|
||||
* renders based on context in the constructor
|
||||
* supports this.context passed via getChildContext
|
||||
|
||||
src/isomorphic/modern/class/__tests__/ReactTypeScriptClass-test.ts
|
||||
* renders based on context in the constructor
|
||||
* supports this.context passed via getChildContext
|
||||
|
||||
src/isomorphic/modern/element/__tests__/ReactJSXElementValidator-test.js
|
||||
* should give context for PropType errors in nested components.
|
||||
|
||||
@@ -441,13 +425,6 @@ src/renderers/shared/stack/reconciler/__tests__/ReactCompositeComponent-test.js
|
||||
* should warn about `setState` on unmounted components
|
||||
* should warn about `setState` in render
|
||||
* should warn about `setState` in getChildContext
|
||||
* should pass context to children when not owner
|
||||
* should pass context when re-rendered for static child
|
||||
* should pass context when re-rendered for static child within a composite component
|
||||
* should pass context transitively
|
||||
* should pass context when re-rendered
|
||||
* unmasked context propagates through updates
|
||||
* should trigger componentWillReceiveProps for context changes
|
||||
* should update refs if shouldComponentUpdate gives false
|
||||
* should support objects with prototypes as state
|
||||
|
||||
@@ -467,10 +444,8 @@ src/renderers/shared/stack/reconciler/__tests__/ReactMultiChildText-test.js
|
||||
* should throw if rendering both HTML and children
|
||||
|
||||
src/renderers/shared/stack/reconciler/__tests__/ReactStatelessComponent-test.js
|
||||
* should pass context thru stateless component
|
||||
* should warn when stateless component returns array
|
||||
* should throw on string refs in pure functions
|
||||
* should receive context
|
||||
* should warn when using non-React functions in JSX
|
||||
|
||||
src/renderers/shared/stack/reconciler/__tests__/ReactUpdates-test.js
|
||||
|
||||
@@ -12,9 +12,6 @@ src/isomorphic/classic/element/__tests__/ReactElementValidator-test.js
|
||||
* warns for keys with component stack info
|
||||
* should give context for PropType errors in nested components.
|
||||
|
||||
src/isomorphic/modern/element/__tests__/ReactJSXElementValidator-test.js
|
||||
* should warn on invalid context types
|
||||
|
||||
src/renderers/dom/shared/__tests__/CSSPropertyOperations-test.js
|
||||
* should warn when using hyphenated style names
|
||||
* should warn when updating hyphenated style names
|
||||
|
||||
@@ -128,6 +128,10 @@ src/isomorphic/children/__tests__/sliceChildren-test.js
|
||||
* should allow static children to be sliced
|
||||
* should slice nested children
|
||||
|
||||
src/isomorphic/classic/__tests__/ReactContextValidator-test.js
|
||||
* should filter out context not in contextTypes
|
||||
* should pass next context to lifecycles
|
||||
|
||||
src/isomorphic/classic/class/__tests__/ReactBind-test.js
|
||||
* warns if you try to bind to this
|
||||
* does not warn if you pass an auto-bound method to setState
|
||||
@@ -149,6 +153,7 @@ src/isomorphic/classic/class/__tests__/ReactClass-test.js
|
||||
* should throw if a reserved property is in statics
|
||||
* should support statics
|
||||
* should work with object getInitialState() return values
|
||||
* renders based on context getInitialState
|
||||
* should throw with non-object getInitialState() return values
|
||||
* should work with a null getInitialState() return value
|
||||
* should throw when using legacy factories
|
||||
@@ -354,6 +359,7 @@ src/isomorphic/modern/class/__tests__/ReactCoffeeScriptClass-test.coffee
|
||||
* renders a simple stateless component with prop
|
||||
* renders based on state using initial values in this.props
|
||||
* renders based on state using props in the constructor
|
||||
* renders based on context in the constructor
|
||||
* renders only once when setting state in componentWillMount
|
||||
* should throw with non-object in the initial state property
|
||||
* should render with null in the initial state property
|
||||
@@ -365,6 +371,7 @@ src/isomorphic/modern/class/__tests__/ReactCoffeeScriptClass-test.coffee
|
||||
* should warn when misspelling shouldComponentUpdate
|
||||
* should warn when misspelling componentWillReceiveProps
|
||||
* should throw AND warn when trying to access classic APIs
|
||||
* supports this.context passed via getChildContext
|
||||
* supports classic refs
|
||||
* supports drilling through to the DOM using findDOMNode
|
||||
|
||||
@@ -374,6 +381,7 @@ src/isomorphic/modern/class/__tests__/ReactES6Class-test.js
|
||||
* renders a simple stateless component with prop
|
||||
* renders based on state using initial values in this.props
|
||||
* renders based on state using props in the constructor
|
||||
* renders based on context in the constructor
|
||||
* renders only once when setting state in componentWillMount
|
||||
* should throw with non-object in the initial state property
|
||||
* should render with null in the initial state property
|
||||
@@ -385,6 +393,7 @@ src/isomorphic/modern/class/__tests__/ReactES6Class-test.js
|
||||
* should warn when misspelling shouldComponentUpdate
|
||||
* should warn when misspelling componentWillReceiveProps
|
||||
* should throw AND warn when trying to access classic APIs
|
||||
* supports this.context passed via getChildContext
|
||||
* supports classic refs
|
||||
* supports drilling through to the DOM using findDOMNode
|
||||
|
||||
@@ -399,6 +408,7 @@ src/isomorphic/modern/class/__tests__/ReactTypeScriptClass-test.ts
|
||||
* renders a simple stateless component with prop
|
||||
* renders based on state using initial values in this.props
|
||||
* renders based on state using props in the constructor
|
||||
* renders based on context in the constructor
|
||||
* renders only once when setting state in componentWillMount
|
||||
* should throw with non-object in the initial state property
|
||||
* should render with null in the initial state property
|
||||
@@ -410,6 +420,7 @@ src/isomorphic/modern/class/__tests__/ReactTypeScriptClass-test.ts
|
||||
* should warn when misspelling shouldComponentUpdate
|
||||
* should warn when misspelling componentWillReceiveProps
|
||||
* should throw AND warn when trying to access classic APIs
|
||||
* supports this.context passed via getChildContext
|
||||
* supports classic refs
|
||||
* supports drilling through to the DOM using findDOMNode
|
||||
|
||||
@@ -446,6 +457,7 @@ src/isomorphic/modern/element/__tests__/ReactJSXElementValidator-test.js
|
||||
* should not check the default for explicit null
|
||||
* should check declared prop types
|
||||
* should warn on invalid prop types
|
||||
* should warn on invalid context types
|
||||
* should warn if getDefaultProps is specificed on the class
|
||||
|
||||
src/renderers/dom/__tests__/ReactDOMProduction-test.js
|
||||
@@ -806,6 +818,11 @@ src/renderers/shared/fiber/__tests__/ReactIncremental-test.js
|
||||
* performs batched updates at the end of the batch
|
||||
* can nest batchedUpdates
|
||||
* can handle if setState callback throws
|
||||
* merges and masks context
|
||||
* does not leak own context into context provider
|
||||
* provides context when reusing work
|
||||
* reads context when setState is below the provider
|
||||
* reads context when setState is above the provider
|
||||
|
||||
src/renderers/shared/fiber/__tests__/ReactIncrementalErrorHandling-test.js
|
||||
* catches render error in a boundary during mounting
|
||||
@@ -1018,7 +1035,14 @@ src/renderers/shared/stack/reconciler/__tests__/ReactCompositeComponent-test.js
|
||||
* should call componentWillUnmount before unmounting
|
||||
* should warn when shouldComponentUpdate() returns undefined
|
||||
* should warn when componentDidUnmount method is defined
|
||||
* should pass context to children when not owner
|
||||
* should skip update when rerendering element in container
|
||||
* should pass context when re-rendered for static child
|
||||
* should pass context when re-rendered for static child within a composite component
|
||||
* should pass context transitively
|
||||
* should pass context when re-rendered
|
||||
* unmasked context propagates through updates
|
||||
* should trigger componentWillReceiveProps for context changes
|
||||
* only renders once if updated in componentWillReceiveProps
|
||||
* should allow access to findDOMNode in componentWillUnmount
|
||||
* context should be passed down from the parent
|
||||
@@ -1147,9 +1171,11 @@ src/renderers/shared/stack/reconciler/__tests__/ReactStatelessComponent-test.js
|
||||
* should render stateless component
|
||||
* should update stateless component
|
||||
* should unmount stateless component
|
||||
* should pass context thru stateless component
|
||||
* should provide a null ref
|
||||
* should use correct name in key warning
|
||||
* should support default props and prop types
|
||||
* should receive context
|
||||
* should work with arrow functions
|
||||
* should allow simple functions to return null
|
||||
* should allow simple functions to return false
|
||||
|
||||
@@ -70,11 +70,10 @@ describe('ReactContextValidator', () => {
|
||||
expect(instance.refs.child.context).toEqual({foo: 'abc'});
|
||||
});
|
||||
|
||||
it('should filter context properly in callbacks', () => {
|
||||
it('should pass next context to lifecycles', () => {
|
||||
var actualComponentWillReceiveProps;
|
||||
var actualShouldComponentUpdate;
|
||||
var actualComponentWillUpdate;
|
||||
var actualComponentDidUpdate;
|
||||
|
||||
var Parent = React.createClass({
|
||||
childContextTypes: {
|
||||
@@ -113,6 +112,45 @@ describe('ReactContextValidator', () => {
|
||||
actualComponentWillUpdate = nextContext;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <div />;
|
||||
},
|
||||
});
|
||||
|
||||
var container = document.createElement('div');
|
||||
ReactDOM.render(<Parent foo="abc" />, container);
|
||||
ReactDOM.render(<Parent foo="def" />, container);
|
||||
expect(actualComponentWillReceiveProps).toEqual({foo: 'def'});
|
||||
expect(actualShouldComponentUpdate).toEqual({foo: 'def'});
|
||||
expect(actualComponentWillUpdate).toEqual({foo: 'def'});
|
||||
});
|
||||
|
||||
it('should pass previous context to lifecycles', () => {
|
||||
var actualComponentDidUpdate;
|
||||
|
||||
var Parent = React.createClass({
|
||||
childContextTypes: {
|
||||
foo: React.PropTypes.string.isRequired,
|
||||
bar: React.PropTypes.string.isRequired,
|
||||
},
|
||||
|
||||
getChildContext: function() {
|
||||
return {
|
||||
foo: this.props.foo,
|
||||
bar: 'bar',
|
||||
};
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <Component />;
|
||||
},
|
||||
});
|
||||
|
||||
var Component = React.createClass({
|
||||
contextTypes: {
|
||||
foo: React.PropTypes.string,
|
||||
},
|
||||
|
||||
componentDidUpdate: function(prevProps, prevState, prevContext) {
|
||||
actualComponentDidUpdate = prevContext;
|
||||
},
|
||||
@@ -125,9 +163,6 @@ describe('ReactContextValidator', () => {
|
||||
var container = document.createElement('div');
|
||||
ReactDOM.render(<Parent foo="abc" />, container);
|
||||
ReactDOM.render(<Parent foo="def" />, container);
|
||||
expect(actualComponentWillReceiveProps).toEqual({foo: 'def'});
|
||||
expect(actualShouldComponentUpdate).toEqual({foo: 'def'});
|
||||
expect(actualComponentWillUpdate).toEqual({foo: 'def'});
|
||||
expect(actualComponentDidUpdate).toEqual({foo: 'abc'});
|
||||
});
|
||||
|
||||
|
||||
@@ -23,7 +23,15 @@ var {
|
||||
reconcileChildFibersInPlace,
|
||||
cloneChildFibers,
|
||||
} = require('ReactChildFiber');
|
||||
|
||||
var ReactTypeOfWork = require('ReactTypeOfWork');
|
||||
var {
|
||||
getMaskedContext,
|
||||
isContextProvider,
|
||||
hasContextChanged,
|
||||
pushContextProvider,
|
||||
resetContext,
|
||||
} = require('ReactFiberContext');
|
||||
var {
|
||||
IndeterminateComponent,
|
||||
FunctionalComponent,
|
||||
@@ -144,6 +152,7 @@ module.exports = function<T, P, I, TI, C>(
|
||||
function updateFunctionalComponent(current, workInProgress) {
|
||||
var fn = workInProgress.type;
|
||||
var props = workInProgress.pendingProps;
|
||||
var context = getMaskedContext(workInProgress);
|
||||
|
||||
// TODO: Disable this before release, since it is not part of the public API
|
||||
// I use this for testing to compare the relative overhead of classes.
|
||||
@@ -159,9 +168,9 @@ module.exports = function<T, P, I, TI, C>(
|
||||
|
||||
if (__DEV__) {
|
||||
ReactCurrentOwner.current = workInProgress;
|
||||
nextChildren = fn(props);
|
||||
nextChildren = fn(props, context);
|
||||
} else {
|
||||
nextChildren = fn(props);
|
||||
nextChildren = fn(props, context);
|
||||
}
|
||||
reconcileChildren(current, workInProgress, nextChildren);
|
||||
return workInProgress.child;
|
||||
@@ -190,6 +199,10 @@ module.exports = function<T, P, I, TI, C>(
|
||||
ReactCurrentOwner.current = workInProgress;
|
||||
const nextChildren = instance.render();
|
||||
reconcileChildren(current, workInProgress, nextChildren);
|
||||
// Put context on the stack because we will work on children
|
||||
if (isContextProvider(workInProgress)) {
|
||||
pushContextProvider(workInProgress, true);
|
||||
}
|
||||
return workInProgress.child;
|
||||
}
|
||||
|
||||
@@ -249,13 +262,15 @@ module.exports = function<T, P, I, TI, C>(
|
||||
}
|
||||
var fn = workInProgress.type;
|
||||
var props = workInProgress.pendingProps;
|
||||
var context = getMaskedContext(workInProgress);
|
||||
|
||||
var value;
|
||||
|
||||
if (__DEV__) {
|
||||
ReactCurrentOwner.current = workInProgress;
|
||||
value = fn(props);
|
||||
value = fn(props, context);
|
||||
} else {
|
||||
value = fn(props);
|
||||
value = fn(props, context);
|
||||
}
|
||||
|
||||
if (typeof value === 'object' && value && typeof value.render === 'function') {
|
||||
@@ -345,6 +360,10 @@ module.exports = function<T, P, I, TI, C>(
|
||||
|
||||
cloneChildFibers(current, workInProgress);
|
||||
markChildAsProgressed(current, workInProgress, priorityLevel);
|
||||
// Put context on the stack because we will work on children
|
||||
if (isContextProvider(workInProgress)) {
|
||||
pushContextProvider(workInProgress, false);
|
||||
}
|
||||
return workInProgress.child;
|
||||
}
|
||||
|
||||
@@ -355,6 +374,11 @@ module.exports = function<T, P, I, TI, C>(
|
||||
}
|
||||
|
||||
function beginWork(current : ?Fiber, workInProgress : Fiber, priorityLevel : PriorityLevel) : ?Fiber {
|
||||
if (!workInProgress.return) {
|
||||
// Don't start new work with context on the stack.
|
||||
resetContext();
|
||||
}
|
||||
|
||||
if (workInProgress.pendingWorkPriority === NoWork ||
|
||||
workInProgress.pendingWorkPriority > priorityLevel) {
|
||||
return bailoutOnLowPriority(current, workInProgress);
|
||||
@@ -375,7 +399,8 @@ module.exports = function<T, P, I, TI, C>(
|
||||
workInProgress.memoizedProps !== null &&
|
||||
workInProgress.pendingProps === workInProgress.memoizedProps
|
||||
)) &&
|
||||
workInProgress.updateQueue === null) {
|
||||
workInProgress.updateQueue === null &&
|
||||
!hasContextChanged()) {
|
||||
return bailoutOnAlreadyFinishedWork(current, workInProgress);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,19 +15,21 @@
|
||||
import type { Fiber } from 'ReactFiber';
|
||||
import type { UpdateQueue } from 'ReactFiberUpdateQueue';
|
||||
|
||||
var {
|
||||
getMaskedContext,
|
||||
} = require('ReactFiberContext');
|
||||
var {
|
||||
createUpdateQueue,
|
||||
addToQueue,
|
||||
addCallbackToQueue,
|
||||
mergeUpdateQueue,
|
||||
} = require('ReactFiberUpdateQueue');
|
||||
var { isMounted } = require('ReactFiberTreeReflection');
|
||||
var { getComponentName, isMounted } = require('ReactFiberTreeReflection');
|
||||
var ReactInstanceMap = require('ReactInstanceMap');
|
||||
var shallowEqual = require('shallowEqual');
|
||||
var warning = require('warning');
|
||||
var invariant = require('invariant');
|
||||
|
||||
|
||||
const isArray = Array.isArray;
|
||||
|
||||
module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
|
||||
@@ -74,7 +76,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 +84,14 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
|
||||
|
||||
const instance = workInProgress.stateNode;
|
||||
if (typeof instance.shouldComponentUpdate === 'function') {
|
||||
const shouldUpdate = instance.shouldComponentUpdate(newProps, newState);
|
||||
const shouldUpdate = instance.shouldComponentUpdate(newProps, newState, newContext);
|
||||
|
||||
if (__DEV__) {
|
||||
warning(
|
||||
shouldUpdate !== undefined,
|
||||
'%s.shouldComponentUpdate(): Returned undefined instead of a ' +
|
||||
'boolean value. Make sure to return true or false.',
|
||||
getName(workInProgress, instance)
|
||||
getComponentName(workInProgress)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -107,20 +109,11 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function getName(workInProgress: Fiber, inst: any): string {
|
||||
const type = workInProgress.type;
|
||||
const constructor = inst && inst.constructor;
|
||||
return (
|
||||
type.displayName || (constructor && constructor.displayName) ||
|
||||
type.name || (constructor && constructor.name) ||
|
||||
'A Component'
|
||||
);
|
||||
}
|
||||
|
||||
function checkClassInstance(workInProgress: Fiber, inst: any) {
|
||||
function checkClassInstance(workInProgress: Fiber) {
|
||||
const instance = workInProgress.stateNode;
|
||||
if (__DEV__) {
|
||||
const name = getName(workInProgress, inst);
|
||||
const renderPresent = inst.render;
|
||||
const name = getComponentName(workInProgress);
|
||||
const renderPresent = instance.render;
|
||||
warning(
|
||||
renderPresent,
|
||||
'%s(...): No `render` method found on the returned component ' +
|
||||
@@ -128,8 +121,8 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
|
||||
name
|
||||
);
|
||||
const noGetInitialStateOnES6 = (
|
||||
!inst.getInitialState ||
|
||||
inst.getInitialState.isReactClassApproved
|
||||
!instance.getInitialState ||
|
||||
instance.getInitialState.isReactClassApproved
|
||||
);
|
||||
warning(
|
||||
noGetInitialStateOnES6,
|
||||
@@ -139,8 +132,8 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
|
||||
name
|
||||
);
|
||||
const noGetDefaultPropsOnES6 = (
|
||||
!inst.getDefaultProps ||
|
||||
inst.getDefaultProps.isReactClassApproved
|
||||
!instance.getDefaultProps ||
|
||||
instance.getDefaultProps.isReactClassApproved
|
||||
);
|
||||
warning(
|
||||
noGetDefaultPropsOnES6,
|
||||
@@ -149,21 +142,21 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
|
||||
'Use a static property to define defaultProps instead.',
|
||||
name
|
||||
);
|
||||
const noInstancePropTypes = !inst.propTypes;
|
||||
const noInstancePropTypes = !instance.propTypes;
|
||||
warning(
|
||||
noInstancePropTypes,
|
||||
'propTypes was defined as an instance property on %s. Use a static ' +
|
||||
'property to define propTypes instead.',
|
||||
name,
|
||||
);
|
||||
const noInstanceContextTypes = !inst.contextTypes;
|
||||
const noInstanceContextTypes = !instance.contextTypes;
|
||||
warning(
|
||||
noInstanceContextTypes,
|
||||
'contextTypes was defined as an instance property on %s. Use a static ' +
|
||||
'property to define contextTypes instead.',
|
||||
name,
|
||||
);
|
||||
const noComponentShouldUpdate = typeof inst.componentShouldUpdate !== 'function';
|
||||
const noComponentShouldUpdate = typeof instance.componentShouldUpdate !== 'function';
|
||||
warning(
|
||||
noComponentShouldUpdate,
|
||||
'%s has a method called ' +
|
||||
@@ -172,7 +165,7 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
|
||||
'expected to return a value.',
|
||||
name
|
||||
);
|
||||
const noComponentDidUnmount = typeof inst.componentDidUnmount !== 'function';
|
||||
const noComponentDidUnmount = typeof instance.componentDidUnmount !== 'function';
|
||||
warning(
|
||||
noComponentDidUnmount,
|
||||
'%s has a method called ' +
|
||||
@@ -180,7 +173,7 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
|
||||
'Did you mean componentWillUnmount()?',
|
||||
name
|
||||
);
|
||||
const noComponentWillRecieveProps = typeof inst.componentWillRecieveProps !== 'function';
|
||||
const noComponentWillRecieveProps = typeof instance.componentWillRecieveProps !== 'function';
|
||||
warning(
|
||||
noComponentWillRecieveProps,
|
||||
'%s has a method called ' +
|
||||
@@ -189,12 +182,20 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
|
||||
);
|
||||
}
|
||||
|
||||
const instanceState = inst.state;
|
||||
if (instanceState && (typeof instanceState !== 'object' || isArray(instanceState))) {
|
||||
const state = instance.state;
|
||||
if (state && (typeof state !== 'object' || isArray(state))) {
|
||||
invariant(
|
||||
false,
|
||||
'%s.state: must be set to an object or null',
|
||||
getName(workInProgress, inst)
|
||||
getComponentName(workInProgress)
|
||||
);
|
||||
}
|
||||
if (typeof instance.getChildContext === 'function') {
|
||||
invariant(
|
||||
typeof workInProgress.type.childContextTypes === 'object',
|
||||
'%s.getChildContext(): childContextTypes must be defined in order to ' +
|
||||
'use getChildContext().',
|
||||
getComponentName(workInProgress)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -209,16 +210,16 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
|
||||
function constructClassInstance(workInProgress : Fiber) : any {
|
||||
const ctor = workInProgress.type;
|
||||
const props = workInProgress.pendingProps;
|
||||
const instance = new ctor(props);
|
||||
checkClassInstance(workInProgress, instance);
|
||||
const context = getMaskedContext(workInProgress);
|
||||
const instance = new ctor(props, context);
|
||||
adoptClassInstance(workInProgress, instance);
|
||||
checkClassInstance(workInProgress);
|
||||
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;
|
||||
|
||||
let props = workInProgress.pendingProps;
|
||||
@@ -228,6 +229,7 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
|
||||
|
||||
instance.props = props;
|
||||
instance.state = state;
|
||||
instance.context = getMaskedContext(workInProgress);
|
||||
|
||||
if (typeof instance.componentWillMount === 'function') {
|
||||
instance.componentWillMount();
|
||||
@@ -253,6 +255,7 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
|
||||
throw new Error('There should always be pending or memoized props.');
|
||||
}
|
||||
}
|
||||
const newContext = getMaskedContext(workInProgress);
|
||||
|
||||
// TODO: Should we deal with a setState that happened after the last
|
||||
// componentWillMount and before this componentWillMount? Probably
|
||||
@@ -262,7 +265,8 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
|
||||
workInProgress,
|
||||
workInProgress.memoizedProps,
|
||||
newProps,
|
||||
newState
|
||||
newState,
|
||||
newContext
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
@@ -272,6 +276,7 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
|
||||
const newInstance = constructClassInstance(workInProgress);
|
||||
newInstance.props = newProps;
|
||||
newInstance.state = newState = newInstance.state || null;
|
||||
newInstance.context = getMaskedContext(workInProgress);
|
||||
|
||||
if (typeof newInstance.componentWillMount === 'function') {
|
||||
newInstance.componentWillMount();
|
||||
@@ -300,34 +305,37 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
|
||||
throw new Error('There should always be pending or memoized props.');
|
||||
}
|
||||
}
|
||||
const oldContext = instance.context;
|
||||
const newContext = getMaskedContext(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) {
|
||||
if (oldProps !== newProps || oldContext !== newContext) {
|
||||
if (typeof instance.componentWillReceiveProps === 'function') {
|
||||
instance.componentWillReceiveProps(newProps);
|
||||
instance.componentWillReceiveProps(newProps, newContext);
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the next state using the memoized state and the update queue.
|
||||
const updateQueue = workInProgress.updateQueue;
|
||||
const previousState = workInProgress.memoizedState;
|
||||
const oldState = workInProgress.memoizedState;
|
||||
// TODO: Previous state can be null.
|
||||
let newState;
|
||||
if (updateQueue) {
|
||||
if (!updateQueue.hasUpdate) {
|
||||
newState = previousState;
|
||||
newState = oldState;
|
||||
} else {
|
||||
newState = mergeUpdateQueue(updateQueue, instance, previousState, newProps);
|
||||
newState = mergeUpdateQueue(updateQueue, instance, oldState, newProps);
|
||||
}
|
||||
} else {
|
||||
newState = previousState;
|
||||
newState = oldState;
|
||||
}
|
||||
|
||||
if (oldProps === newProps &&
|
||||
previousState === newState &&
|
||||
oldState === newState &&
|
||||
oldContext === newContext &&
|
||||
updateQueue && !updateQueue.isForced) {
|
||||
return false;
|
||||
}
|
||||
@@ -336,18 +344,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,10 @@ import type { HostConfig } from 'ReactFiberReconciler';
|
||||
import type { ReifiedYield } from 'ReactReifiedYield';
|
||||
|
||||
var { reconcileChildFibers } = require('ReactChildFiber');
|
||||
var {
|
||||
isContextProvider,
|
||||
popContextProvider,
|
||||
} = require('ReactFiberContext');
|
||||
var ReactTypeOfWork = require('ReactTypeOfWork');
|
||||
var ReactTypeOfSideEffect = require('ReactTypeOfSideEffect');
|
||||
var {
|
||||
@@ -125,6 +129,10 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
|
||||
return null;
|
||||
case ClassComponent:
|
||||
transferOutput(workInProgress.child, workInProgress);
|
||||
// We are leaving this subtree, so pop context if any.
|
||||
if (isContextProvider(workInProgress)) {
|
||||
popContextProvider();
|
||||
}
|
||||
// Don't use the state queue to compute the memoized state. We already
|
||||
// merged it and assigned it to the instance. Transfer it from there.
|
||||
// Also need to transfer the props, because pendingProps will be null
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* 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 ReactFiberContext
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type { Fiber } from 'ReactFiber';
|
||||
|
||||
var emptyObject = require('emptyObject');
|
||||
var invariant = require('invariant');
|
||||
var {
|
||||
getComponentName,
|
||||
} = require('ReactFiberTreeReflection');
|
||||
var {
|
||||
ClassComponent,
|
||||
} = require('ReactTypeOfWork');
|
||||
|
||||
if (__DEV__) {
|
||||
var checkReactTypeSpec = require('checkReactTypeSpec');
|
||||
}
|
||||
|
||||
let index = -1;
|
||||
const contextStack : Array<Object> = [];
|
||||
const didPerformWorkStack : Array<boolean> = [];
|
||||
|
||||
function getUnmaskedContext() {
|
||||
if (index === -1) {
|
||||
return emptyObject;
|
||||
}
|
||||
return contextStack[index];
|
||||
}
|
||||
|
||||
exports.getMaskedContext = function(fiber : Fiber) {
|
||||
const type = fiber.type;
|
||||
const contextTypes = type.contextTypes;
|
||||
if (!contextTypes) {
|
||||
return emptyObject;
|
||||
}
|
||||
|
||||
const unmaskedContext = getUnmaskedContext();
|
||||
const context = {};
|
||||
for (let key in contextTypes) {
|
||||
context[key] = unmaskedContext[key];
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
const name = getComponentName(fiber);
|
||||
const debugID = 0; // TODO: pass a real ID
|
||||
checkReactTypeSpec(contextTypes, context, 'context', name, null, debugID);
|
||||
}
|
||||
|
||||
return context;
|
||||
};
|
||||
|
||||
exports.hasContextChanged = function() : boolean {
|
||||
return index > -1 && didPerformWorkStack[index];
|
||||
};
|
||||
|
||||
exports.isContextProvider = function(fiber : Fiber) : boolean {
|
||||
return (
|
||||
fiber.tag === ClassComponent &&
|
||||
typeof fiber.stateNode.getChildContext === 'function'
|
||||
);
|
||||
};
|
||||
|
||||
exports.popContextProvider = function() : void {
|
||||
contextStack[index] = emptyObject;
|
||||
didPerformWorkStack[index] = false;
|
||||
index--;
|
||||
};
|
||||
|
||||
exports.pushContextProvider = function(fiber : Fiber, didPerformWork : boolean) : void {
|
||||
const instance = fiber.stateNode;
|
||||
const childContextTypes = fiber.type.childContextTypes;
|
||||
|
||||
const memoizedMergedChildContext = instance.__reactInternalMemoizedMergedChildContext;
|
||||
const canReuseMergedChildContext = !didPerformWork && memoizedMergedChildContext != null;
|
||||
|
||||
let mergedContext = null;
|
||||
if (canReuseMergedChildContext) {
|
||||
mergedContext = memoizedMergedChildContext;
|
||||
} else {
|
||||
const childContext = instance.getChildContext();
|
||||
for (let contextKey in childContext) {
|
||||
invariant(
|
||||
contextKey in childContextTypes,
|
||||
'%s.getChildContext(): key "%s" is not defined in childContextTypes.',
|
||||
getComponentName(fiber),
|
||||
contextKey
|
||||
);
|
||||
}
|
||||
if (__DEV__) {
|
||||
const name = getComponentName(fiber);
|
||||
const debugID = 0; // TODO: pass a real ID
|
||||
checkReactTypeSpec(childContextTypes, childContext, 'childContext', name, null, debugID);
|
||||
}
|
||||
mergedContext = {...getUnmaskedContext(), ...childContext};
|
||||
instance.__reactInternalMemoizedMergedChildContext = mergedContext;
|
||||
}
|
||||
|
||||
index++;
|
||||
contextStack[index] = mergedContext;
|
||||
didPerformWorkStack[index] = didPerformWork;
|
||||
};
|
||||
|
||||
exports.resetContext = function() : void {
|
||||
index = -1;
|
||||
};
|
||||
|
||||
@@ -114,3 +114,14 @@ exports.findCurrentHostFiber = function(component : ReactComponent<any, any, any
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
exports.getComponentName = function(fiber: Fiber): string {
|
||||
const type = fiber.type;
|
||||
const instance = fiber.stateNode;
|
||||
const constructor = instance && instance.constructor;
|
||||
return (
|
||||
type.displayName || (constructor && constructor.displayName) ||
|
||||
type.name || (constructor && constructor.name) ||
|
||||
'A Component'
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1509,4 +1509,441 @@ describe('ReactIncremental', () => {
|
||||
]);
|
||||
expect(instance.state.n).toEqual(3);
|
||||
});
|
||||
|
||||
it('merges and masks context', () => {
|
||||
var ops = [];
|
||||
|
||||
class Intl extends React.Component {
|
||||
static childContextTypes = {
|
||||
locale: React.PropTypes.string,
|
||||
};
|
||||
getChildContext() {
|
||||
return {
|
||||
locale: this.props.locale,
|
||||
};
|
||||
}
|
||||
render() {
|
||||
ops.push('Intl ' + JSON.stringify(this.context));
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
class Router extends React.Component {
|
||||
static childContextTypes = {
|
||||
route: React.PropTypes.string,
|
||||
};
|
||||
getChildContext() {
|
||||
return {
|
||||
route: this.props.route,
|
||||
};
|
||||
}
|
||||
render() {
|
||||
ops.push('Router ' + JSON.stringify(this.context));
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
class ShowLocale extends React.Component {
|
||||
static contextTypes = {
|
||||
locale: React.PropTypes.string,
|
||||
};
|
||||
render() {
|
||||
ops.push('ShowLocale ' + JSON.stringify(this.context));
|
||||
return this.context.locale;
|
||||
}
|
||||
}
|
||||
|
||||
class ShowRoute extends React.Component {
|
||||
static contextTypes = {
|
||||
route: React.PropTypes.string,
|
||||
};
|
||||
render() {
|
||||
ops.push('ShowRoute ' + JSON.stringify(this.context));
|
||||
return this.context.route;
|
||||
}
|
||||
}
|
||||
|
||||
function ShowBoth(props, context) {
|
||||
ops.push('ShowBoth ' + JSON.stringify(context));
|
||||
return `${context.route} in ${context.locale}`;
|
||||
}
|
||||
ShowBoth.contextTypes = {
|
||||
locale: React.PropTypes.string,
|
||||
route: React.PropTypes.string,
|
||||
};
|
||||
|
||||
class ShowNeither extends React.Component {
|
||||
render() {
|
||||
ops.push('ShowNeither ' + JSON.stringify(this.context));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class Indirection extends React.Component {
|
||||
render() {
|
||||
ops.push('Indirection ' + JSON.stringify(this.context));
|
||||
return [
|
||||
<ShowLocale />,
|
||||
<ShowRoute />,
|
||||
<ShowNeither />,
|
||||
<Intl locale="ru">
|
||||
<ShowBoth />
|
||||
</Intl>,
|
||||
<ShowBoth />,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
ops.length = 0;
|
||||
ReactNoop.render(
|
||||
<Intl locale="fr">
|
||||
<ShowLocale />
|
||||
<div>
|
||||
<ShowBoth />
|
||||
</div>
|
||||
</Intl>
|
||||
);
|
||||
ReactNoop.flush();
|
||||
expect(ops).toEqual([
|
||||
'Intl {}',
|
||||
'ShowLocale {"locale":"fr"}',
|
||||
'ShowBoth {"locale":"fr"}',
|
||||
]);
|
||||
|
||||
ops.length = 0;
|
||||
ReactNoop.render(
|
||||
<Intl locale="de">
|
||||
<ShowLocale />
|
||||
<div>
|
||||
<ShowBoth />
|
||||
</div>
|
||||
</Intl>
|
||||
);
|
||||
ReactNoop.flush();
|
||||
expect(ops).toEqual([
|
||||
'Intl {}',
|
||||
'ShowLocale {"locale":"de"}',
|
||||
'ShowBoth {"locale":"de"}',
|
||||
]);
|
||||
|
||||
ops.length = 0;
|
||||
ReactNoop.render(
|
||||
<Intl locale="sv">
|
||||
<ShowLocale />
|
||||
<div>
|
||||
<ShowBoth />
|
||||
</div>
|
||||
</Intl>
|
||||
);
|
||||
ReactNoop.flushDeferredPri(15);
|
||||
expect(ops).toEqual([
|
||||
'Intl {}',
|
||||
]);
|
||||
|
||||
ops.length = 0;
|
||||
ReactNoop.render(
|
||||
<Intl locale="en">
|
||||
<ShowLocale />
|
||||
<Router route="/about">
|
||||
<Indirection />
|
||||
</Router>
|
||||
<ShowBoth />
|
||||
</Intl>
|
||||
);
|
||||
ReactNoop.flush();
|
||||
expect(ops).toEqual([
|
||||
'Intl {}',
|
||||
'ShowLocale {"locale":"en"}',
|
||||
'Router {}',
|
||||
'Indirection {}',
|
||||
'ShowLocale {"locale":"en"}',
|
||||
'ShowRoute {"route":"/about"}',
|
||||
'ShowNeither {}',
|
||||
'Intl {}',
|
||||
'ShowBoth {"locale":"ru","route":"/about"}',
|
||||
'ShowBoth {"locale":"en","route":"/about"}',
|
||||
'ShowBoth {"locale":"en"}',
|
||||
]);
|
||||
});
|
||||
|
||||
it('does not leak own context into context provider', () => {
|
||||
var ops = [];
|
||||
class Recurse extends React.Component {
|
||||
static contextTypes = {
|
||||
n: React.PropTypes.number,
|
||||
};
|
||||
static childContextTypes = {
|
||||
n: React.PropTypes.number,
|
||||
};
|
||||
getChildContext() {
|
||||
return {n: (this.context.n || 3) - 1};
|
||||
}
|
||||
render() {
|
||||
ops.push('Recurse ' + JSON.stringify(this.context));
|
||||
if (this.context.n === 0) {
|
||||
return null;
|
||||
}
|
||||
return <Recurse />;
|
||||
}
|
||||
}
|
||||
|
||||
ReactNoop.render(<Recurse />);
|
||||
ReactNoop.flush();
|
||||
expect(ops).toEqual([
|
||||
'Recurse {}',
|
||||
'Recurse {"n":2}',
|
||||
'Recurse {"n":1}',
|
||||
'Recurse {"n":0}',
|
||||
]);
|
||||
});
|
||||
|
||||
it('provides context when reusing work', () => {
|
||||
var ops = [];
|
||||
|
||||
class Intl extends React.Component {
|
||||
static childContextTypes = {
|
||||
locale: React.PropTypes.string,
|
||||
};
|
||||
getChildContext() {
|
||||
return {
|
||||
locale: this.props.locale,
|
||||
};
|
||||
}
|
||||
render() {
|
||||
ops.push('Intl ' + JSON.stringify(this.context));
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
class ShowLocale extends React.Component {
|
||||
static contextTypes = {
|
||||
locale: React.PropTypes.string,
|
||||
};
|
||||
render() {
|
||||
ops.push('ShowLocale ' + JSON.stringify(this.context));
|
||||
return this.context.locale;
|
||||
}
|
||||
}
|
||||
|
||||
ops.length = 0;
|
||||
ReactNoop.render(
|
||||
<Intl locale="fr">
|
||||
<ShowLocale />
|
||||
<div hidden="true">
|
||||
<ShowLocale />
|
||||
<Intl locale="ru">
|
||||
<ShowLocale />
|
||||
</Intl>
|
||||
</div>
|
||||
<ShowLocale />
|
||||
</Intl>
|
||||
);
|
||||
ReactNoop.flushDeferredPri(40);
|
||||
expect(ops).toEqual([
|
||||
'Intl {}',
|
||||
'ShowLocale {"locale":"fr"}',
|
||||
'ShowLocale {"locale":"fr"}',
|
||||
]);
|
||||
|
||||
ops.length = 0;
|
||||
ReactNoop.flush();
|
||||
expect(ops).toEqual([
|
||||
'ShowLocale {"locale":"fr"}',
|
||||
'Intl {}',
|
||||
'ShowLocale {"locale":"ru"}',
|
||||
]);
|
||||
});
|
||||
|
||||
it('reads context when setState is below the provider', () => {
|
||||
var ops = [];
|
||||
var statefulInst;
|
||||
|
||||
class Intl extends React.Component {
|
||||
static childContextTypes = {
|
||||
locale: React.PropTypes.string,
|
||||
};
|
||||
getChildContext() {
|
||||
const childContext = {
|
||||
locale: this.props.locale,
|
||||
};
|
||||
ops.push('Intl:provide ' + JSON.stringify(childContext));
|
||||
return childContext;
|
||||
}
|
||||
render() {
|
||||
ops.push('Intl:read ' + JSON.stringify(this.context));
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
class ShowLocaleClass extends React.Component {
|
||||
static contextTypes = {
|
||||
locale: React.PropTypes.string,
|
||||
};
|
||||
render() {
|
||||
ops.push('ShowLocaleClass:read ' + JSON.stringify(this.context));
|
||||
return this.context.locale;
|
||||
}
|
||||
}
|
||||
|
||||
function ShowLocaleFn(props, context) {
|
||||
ops.push('ShowLocaleFn:read ' + JSON.stringify(context));
|
||||
return context.locale;
|
||||
}
|
||||
ShowLocaleFn.contextTypes = {
|
||||
locale: React.PropTypes.string,
|
||||
};
|
||||
|
||||
class Stateful extends React.Component {
|
||||
state = {x: 0};
|
||||
render() {
|
||||
statefulInst = this;
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
function IndirectionFn(props, context) {
|
||||
ops.push('IndirectionFn ' + JSON.stringify(context));
|
||||
return props.children;
|
||||
}
|
||||
|
||||
class IndirectionClass extends React.Component {
|
||||
render() {
|
||||
ops.push('IndirectionClass ' + JSON.stringify(this.context));
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
ops.length = 0;
|
||||
ReactNoop.render(
|
||||
<Intl locale="fr">
|
||||
<IndirectionFn>
|
||||
<IndirectionClass>
|
||||
<Stateful>
|
||||
<ShowLocaleClass />
|
||||
<ShowLocaleFn />
|
||||
</Stateful>
|
||||
</IndirectionClass>
|
||||
</IndirectionFn>
|
||||
</Intl>
|
||||
);
|
||||
ReactNoop.flush();
|
||||
expect(ops).toEqual([
|
||||
'Intl:read {}',
|
||||
'Intl:provide {"locale":"fr"}',
|
||||
'IndirectionFn {}',
|
||||
'IndirectionClass {}',
|
||||
'ShowLocaleClass:read {"locale":"fr"}',
|
||||
'ShowLocaleFn:read {"locale":"fr"}',
|
||||
]);
|
||||
|
||||
ops.length = 0;
|
||||
statefulInst.setState({x: 1});
|
||||
ReactNoop.flush();
|
||||
// All work has been memoized because setState()
|
||||
// happened below the context and could not have affected it.
|
||||
expect(ops).toEqual([]);
|
||||
});
|
||||
|
||||
it('reads context when setState is above the provider', () => {
|
||||
var ops = [];
|
||||
var statefulInst;
|
||||
|
||||
class Intl extends React.Component {
|
||||
static childContextTypes = {
|
||||
locale: React.PropTypes.string,
|
||||
};
|
||||
getChildContext() {
|
||||
const childContext = {
|
||||
locale: this.props.locale,
|
||||
};
|
||||
ops.push('Intl:provide ' + JSON.stringify(childContext));
|
||||
return childContext;
|
||||
}
|
||||
render() {
|
||||
ops.push('Intl:read ' + JSON.stringify(this.context));
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
class ShowLocaleClass extends React.Component {
|
||||
static contextTypes = {
|
||||
locale: React.PropTypes.string,
|
||||
};
|
||||
render() {
|
||||
ops.push('ShowLocaleClass:read ' + JSON.stringify(this.context));
|
||||
return this.context.locale;
|
||||
}
|
||||
}
|
||||
|
||||
function ShowLocaleFn(props, context) {
|
||||
ops.push('ShowLocaleFn:read ' + JSON.stringify(context));
|
||||
return context.locale;
|
||||
}
|
||||
ShowLocaleFn.contextTypes = {
|
||||
locale: React.PropTypes.string,
|
||||
};
|
||||
|
||||
function IndirectionFn(props, context) {
|
||||
ops.push('IndirectionFn ' + JSON.stringify(context));
|
||||
return props.children;
|
||||
}
|
||||
|
||||
class IndirectionClass extends React.Component {
|
||||
render() {
|
||||
ops.push('IndirectionClass ' + JSON.stringify(this.context));
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
class Stateful extends React.Component {
|
||||
state = {locale: 'fr'};
|
||||
render() {
|
||||
statefulInst = this;
|
||||
return (
|
||||
<Intl locale={this.state.locale}>
|
||||
{this.props.children}
|
||||
</Intl>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ops.length = 0;
|
||||
ReactNoop.render(
|
||||
<Stateful>
|
||||
<IndirectionFn>
|
||||
<IndirectionClass>
|
||||
<ShowLocaleClass />
|
||||
<ShowLocaleFn />
|
||||
</IndirectionClass>
|
||||
</IndirectionFn>
|
||||
</Stateful>
|
||||
);
|
||||
ReactNoop.flush();
|
||||
expect(ops).toEqual([
|
||||
'Intl:read {}',
|
||||
'Intl:provide {"locale":"fr"}',
|
||||
'IndirectionFn {}',
|
||||
'IndirectionClass {}',
|
||||
'ShowLocaleClass:read {"locale":"fr"}',
|
||||
'ShowLocaleFn:read {"locale":"fr"}',
|
||||
]);
|
||||
|
||||
ops.length = 0;
|
||||
statefulInst.setState({locale: 'gr'});
|
||||
ReactNoop.flush();
|
||||
expect(ops).toEqual([
|
||||
// Intl is below setState() so it might have been
|
||||
// affected by it. Therefore we re-render and recompute
|
||||
// its child context.
|
||||
'Intl:read {}',
|
||||
'Intl:provide {"locale":"gr"}',
|
||||
// TODO: it's unfortunate that we can't reuse work on
|
||||
// these components even though they don't depend on context.
|
||||
'IndirectionFn {}',
|
||||
'IndirectionClass {}',
|
||||
// These components depend on context:
|
||||
'ShowLocaleClass:read {"locale":"gr"}',
|
||||
'ShowLocaleFn:read {"locale":"gr"}',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,6 +15,7 @@ var ChildUpdates;
|
||||
var MorphingComponent;
|
||||
var React;
|
||||
var ReactDOM;
|
||||
var ReactDOMFeatureFlags;
|
||||
var ReactCurrentOwner;
|
||||
var ReactPropTypes;
|
||||
var ReactServerRendering;
|
||||
@@ -26,6 +27,7 @@ describe('ReactCompositeComponent', () => {
|
||||
jest.resetModuleRegistry();
|
||||
React = require('React');
|
||||
ReactDOM = require('ReactDOM');
|
||||
ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
|
||||
ReactCurrentOwner = require('ReactCurrentOwner');
|
||||
ReactPropTypes = require('ReactPropTypes');
|
||||
ReactTestUtils = require('ReactTestUtils');
|
||||
@@ -830,7 +832,10 @@ describe('ReactCompositeComponent', () => {
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState, prevContext) {
|
||||
expect('foo' in prevContext).toBe(true);
|
||||
if (!ReactDOMFeatureFlags.useFiber) {
|
||||
// Fiber does not pass the previous context.
|
||||
expect('foo' in prevContext).toBe(true);
|
||||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState, nextContext) {
|
||||
@@ -849,7 +854,10 @@ describe('ReactCompositeComponent', () => {
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState, prevContext) {
|
||||
expect('foo' in prevContext).toBe(false);
|
||||
if (!ReactDOMFeatureFlags.useFiber) {
|
||||
// Fiber does not pass the previous context.
|
||||
expect('foo' in prevContext).toBe(false);
|
||||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState, nextContext) {
|
||||
@@ -971,21 +979,16 @@ describe('ReactCompositeComponent', () => {
|
||||
};
|
||||
}
|
||||
|
||||
onClick = () => {
|
||||
this.setState({
|
||||
foo: 'def',
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
return <div className="parent" onClick={this.onClick}>{this.props.children}</div>;
|
||||
return <div className="parent">{this.props.children}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
var div = document.createElement('div');
|
||||
|
||||
var parentInstance = null;
|
||||
ReactDOM.render(
|
||||
<Parent>
|
||||
<Parent ref={inst => parentInstance = inst}>
|
||||
<ChildWithoutContext>
|
||||
A1
|
||||
<GrandChild>A2</GrandChild>
|
||||
@@ -999,7 +1002,9 @@ describe('ReactCompositeComponent', () => {
|
||||
div
|
||||
);
|
||||
|
||||
ReactTestUtils.Simulate.click(div.childNodes[0]);
|
||||
parentInstance.setState({
|
||||
foo: 'def',
|
||||
});
|
||||
|
||||
expect(propChanges).toBe(0);
|
||||
expect(contextChanges).toBe(3); // ChildWithContext, GrandChild x 2
|
||||
|
||||
Reference in New Issue
Block a user