Memoize merged child context when possible

We can avoid recreating the merged context object if the context provider work was reused. This is essential to avoid allocations for deep setState() calls.
This commit is contained in:
Dan Abramov
2016-11-15 14:41:59 +00:00
parent 299009c41f
commit a6ee5b876a
2 changed files with 28 additions and 16 deletions
+23 -14
View File
@@ -80,23 +80,32 @@ exports.popContextProvider = function() : void {
exports.pushContextProvider = function(fiber : Fiber, didPerformWork : boolean) : void {
const instance = fiber.stateNode;
const childContextTypes = fiber.type.childContextTypes;
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);
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;
}
const mergedContext = Object.assign({}, getUnmaskedContext(), childContext);
index++;
contextStack[index] = mergedContext;
didPerformWorkStack[index] = didPerformWork;
@@ -1822,8 +1822,8 @@ describe('ReactIncremental', () => {
statefulInst.setState({x: 1});
ReactNoop.flush();
expect(ops).toEqual([
// TODO: we should be able to reuse the previous child context.
'Intl:provide {"locale":"fr"}',
// Intl was memoized so we did not need to
// either render it or recompute its context.
'ShowLocaleClass:read {"locale":"fr"}',
'ShowLocaleFn:read {"locale":"fr"}',
]);
@@ -1895,6 +1895,9 @@ describe('ReactIncremental', () => {
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 null',
'Intl:provide {"locale":"gr"}',
'ShowLocaleClass:read {"locale":"gr"}',