mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Merge pull request #8706 from bvaughn/dont-recreate-masked-context-unless-needed
Dont recreate maked context unless unmasked context changes
This commit is contained in:
@@ -1165,6 +1165,7 @@ src/renderers/shared/fiber/__tests__/ReactIncremental-test.js
|
||||
* reads context when setState is above the provider
|
||||
* maintains the correct context when providers bail out due to low priority
|
||||
* maintains the correct context when unwinding due to an error in render
|
||||
* should not recreate masked context unless inputs have changed
|
||||
|
||||
src/renderers/shared/fiber/__tests__/ReactIncrementalErrorHandling-test.js
|
||||
* catches render error in a boundary during full deferred mounting
|
||||
|
||||
@@ -31,6 +31,7 @@ var {
|
||||
var ReactTypeOfWork = require('ReactTypeOfWork');
|
||||
var {
|
||||
getMaskedContext,
|
||||
getUnmaskedContext,
|
||||
hasContextChanged,
|
||||
pushContextProvider,
|
||||
pushTopLevelContextObject,
|
||||
@@ -210,7 +211,8 @@ module.exports = function<T, P, I, TI, C, CX, CI>(
|
||||
return bailoutOnAlreadyFinishedWork(current, workInProgress);
|
||||
}
|
||||
|
||||
var context = getMaskedContext(workInProgress);
|
||||
var unmaskedContext = getUnmaskedContext(workInProgress);
|
||||
var context = getMaskedContext(workInProgress, unmaskedContext);
|
||||
|
||||
var nextChildren;
|
||||
|
||||
@@ -427,7 +429,8 @@ module.exports = function<T, P, I, TI, C, CX, CI>(
|
||||
}
|
||||
var fn = workInProgress.type;
|
||||
var props = workInProgress.pendingProps;
|
||||
var context = getMaskedContext(workInProgress);
|
||||
var unmaskedContext = getUnmaskedContext(workInProgress);
|
||||
var context = getMaskedContext(workInProgress, unmaskedContext);
|
||||
|
||||
var value;
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import type { PriorityLevel } from 'ReactPriorityLevel';
|
||||
|
||||
var {
|
||||
getMaskedContext,
|
||||
getUnmaskedContext,
|
||||
} = require('ReactFiberContext');
|
||||
var {
|
||||
addUpdate,
|
||||
@@ -204,10 +205,17 @@ module.exports = function(
|
||||
function constructClassInstance(workInProgress : Fiber) : any {
|
||||
const ctor = workInProgress.type;
|
||||
const props = workInProgress.pendingProps;
|
||||
const context = getMaskedContext(workInProgress);
|
||||
const unmaskedContext = getUnmaskedContext(workInProgress);
|
||||
const context = getMaskedContext(workInProgress, unmaskedContext);
|
||||
const instance = new ctor(props, context);
|
||||
adoptClassInstance(workInProgress, instance);
|
||||
checkClassInstance(workInProgress);
|
||||
|
||||
// Cache unmasked context so we can avoid recreating masked context unless necessary.
|
||||
// ReactFiberContext usually updates this cache but can't for newly-created instances.
|
||||
instance.__reactInternalMemoizedUnmaskedChildContext = unmaskedContext;
|
||||
instance.__reactInternalMemoizedMaskedChildContext = context;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
@@ -221,9 +229,11 @@ module.exports = function(
|
||||
throw new Error('There must be pending props for an initial mount.');
|
||||
}
|
||||
|
||||
const unmaskedContext = getUnmaskedContext(workInProgress);
|
||||
|
||||
instance.props = props;
|
||||
instance.state = state;
|
||||
instance.context = getMaskedContext(workInProgress);
|
||||
instance.context = getMaskedContext(workInProgress, unmaskedContext);
|
||||
|
||||
if (typeof instance.componentWillMount === 'function') {
|
||||
instance.componentWillMount();
|
||||
@@ -256,7 +266,8 @@ module.exports = function(
|
||||
throw new Error('There should always be pending or memoized props.');
|
||||
}
|
||||
}
|
||||
const newContext = getMaskedContext(workInProgress);
|
||||
const newUnmaskedContext = getUnmaskedContext(workInProgress);
|
||||
const newContext = getMaskedContext(workInProgress, newUnmaskedContext);
|
||||
|
||||
// TODO: Should we deal with a setState that happened after the last
|
||||
// componentWillMount and before this componentWillMount? Probably
|
||||
@@ -277,7 +288,7 @@ module.exports = function(
|
||||
const newInstance = constructClassInstance(workInProgress);
|
||||
newInstance.props = newProps;
|
||||
newInstance.state = newState = newInstance.state || null;
|
||||
newInstance.context = getMaskedContext(workInProgress);
|
||||
newInstance.context = newContext;
|
||||
|
||||
if (typeof newInstance.componentWillMount === 'function') {
|
||||
newInstance.componentWillMount();
|
||||
@@ -314,7 +325,8 @@ module.exports = function(
|
||||
}
|
||||
}
|
||||
const oldContext = instance.context;
|
||||
const newContext = getMaskedContext(workInProgress);
|
||||
const newUnmaskedContext = getUnmaskedContext(workInProgress);
|
||||
const newContext = getMaskedContext(workInProgress, newUnmaskedContext);
|
||||
|
||||
// Note: During these life-cycles, instance.props/instance.state are what
|
||||
// ever the previously attempted to render - not the "current". However,
|
||||
|
||||
@@ -55,15 +55,26 @@ function getUnmaskedContext(workInProgress : Fiber) : Object {
|
||||
}
|
||||
return contextStackCursor.current;
|
||||
}
|
||||
exports.getUnmaskedContext = getUnmaskedContext;
|
||||
|
||||
exports.getMaskedContext = function(workInProgress : Fiber) {
|
||||
exports.getMaskedContext = function(workInProgress : Fiber, unmaskedContext : Object) {
|
||||
const type = workInProgress.type;
|
||||
const contextTypes = type.contextTypes;
|
||||
if (!contextTypes) {
|
||||
return emptyObject;
|
||||
}
|
||||
|
||||
const unmaskedContext = getUnmaskedContext(workInProgress);
|
||||
// Avoid recreating masked context unless unmasked context has changed.
|
||||
// Failing to do this will result in unnecessary calls to componentWillReceiveProps.
|
||||
// This may trigger infinite loops if componentWillReceiveProps calls setState.
|
||||
const instance = workInProgress.stateNode;
|
||||
if (
|
||||
instance &&
|
||||
instance.__reactInternalMemoizedUnmaskedChildContext === unmaskedContext
|
||||
) {
|
||||
return instance.__reactInternalMemoizedMaskedChildContext;
|
||||
}
|
||||
|
||||
const context = {};
|
||||
for (let key in contextTypes) {
|
||||
context[key] = unmaskedContext[key];
|
||||
@@ -74,6 +85,12 @@ exports.getMaskedContext = function(workInProgress : Fiber) {
|
||||
checkReactTypeSpec(contextTypes, context, 'context', name, null, workInProgress);
|
||||
}
|
||||
|
||||
// Cache unmasked context so we can avoid recreating masked context unless necessary.
|
||||
if (instance) {
|
||||
instance.__reactInternalMemoizedUnmaskedChildContext = unmaskedContext;
|
||||
instance.__reactInternalMemoizedMaskedChildContext = context;
|
||||
}
|
||||
|
||||
return context;
|
||||
};
|
||||
|
||||
|
||||
@@ -2021,4 +2021,50 @@ describe('ReactIncremental', () => {
|
||||
});
|
||||
ReactNoop.flush();
|
||||
});
|
||||
|
||||
it('should not recreate masked context unless inputs have changed', () => {
|
||||
const ops = [];
|
||||
|
||||
let scuCounter = 0;
|
||||
|
||||
class MyComponent extends React.Component {
|
||||
static contextTypes = {};
|
||||
componentDidMount(prevProps, prevState) {
|
||||
ops.push('componentDidMount');
|
||||
this.setState({ setStateInCDU: true });
|
||||
}
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
ops.push('componentDidUpdate');
|
||||
if (this.state.setStateInCDU) {
|
||||
this.setState({ setStateInCDU: false });
|
||||
}
|
||||
}
|
||||
componentWillReceiveProps(nextProps) {
|
||||
ops.push('componentWillReceiveProps');
|
||||
this.setState({ setStateInCDU: true });
|
||||
}
|
||||
render() {
|
||||
ops.push('render');
|
||||
return null;
|
||||
}
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
ops.push('shouldComponentUpdate');
|
||||
return scuCounter++ < 5; // Don't let test hang
|
||||
}
|
||||
}
|
||||
|
||||
ReactNoop.render(<MyComponent />);
|
||||
ReactNoop.flush();
|
||||
|
||||
expect(ops).toEqual([
|
||||
'render',
|
||||
'componentDidMount',
|
||||
'shouldComponentUpdate',
|
||||
'render',
|
||||
'componentDidUpdate',
|
||||
'shouldComponentUpdate',
|
||||
'render',
|
||||
'componentDidUpdate',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user