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.
Dan Abramov committed
Nov 15, 2016 at 14:41 UTC
a6ee5b876a90e87efe9fe2f3605bef9347fa4327
2 files changed
+29
-17
src/renderers/shared/fiber/ReactFiberContext.js
+24
-15
@@ -80,23 +80,32 @@ exports.popContextProvider = function() : void {
80
exports.pushContextProvider = function(fiber : Fiber, didPerformWork : boolean) : void {
81
const instance = fiber.stateNode;
82
const childContextTypes = fiber.type.childContextTypes;
83
- const childContext = instance.getChildContext();
84
-
85
- for (let contextKey in childContext) {
86
- invariant(
87
- contextKey in childContextTypes,
88
- '%s.getChildContext(): key "%s" is not defined in childContextTypes.',
89
- getComponentName(fiber),
90
- contextKey
91
- );
92
- }
93
- if (__DEV__) {
94
- const name = getComponentName(fiber);
95
- const debugID = 0; // TODO: pass a real ID
96
- checkReactTypeSpec(childContextTypes, childContext, 'childContext', name, null, debugID);
83
+
84
+ const memoizedMergedChildContext = instance.__reactInternalMemoizedMergedChildContext;
85
+ const canReuseMergedChildContext = !didPerformWork && memoizedMergedChildContext != null;
86
+
87
+ let mergedContext = null;
88
+ if (canReuseMergedChildContext) {
89
+ mergedContext = memoizedMergedChildContext;
90
+ } else {
91
+ const childContext = instance.getChildContext();
92
+ for (let contextKey in childContext) {
93
+ invariant(
94
+ contextKey in childContextTypes,
95
+ '%s.getChildContext(): key "%s" is not defined in childContextTypes.',
96
+ getComponentName(fiber),
97
+ contextKey
98
+ );
99
+ }
100
+ if (__DEV__) {
101
+ const name = getComponentName(fiber);
102
+ const debugID = 0; // TODO: pass a real ID
103
+ checkReactTypeSpec(childContextTypes, childContext, 'childContext', name, null, debugID);
104
+ }
105
+ mergedContext = {...getUnmaskedContext(), ...childContext};
106
+ instance.__reactInternalMemoizedMergedChildContext = mergedContext;
107
}
108
99
- const mergedContext = Object.assign({}, getUnmaskedContext(), childContext);
109
index++;
110
contextStack[index] = mergedContext;
111
didPerformWorkStack[index] = didPerformWork;
src/renderers/shared/fiber/__tests__/ReactIncremental-test.js
+5
-2
@@ -1822,8 +1822,8 @@ describe('ReactIncremental', () => {
1822
statefulInst.setState({x: 1});
1823
ReactNoop.flush();
1824
expect(ops).toEqual([
1825
- // TODO: we should be able to reuse the previous child context.
1826
- 'Intl:provide {"locale":"fr"}',
1825
+ // Intl was memoized so we did not need to
1826
+ // either render it or recompute its context.
1827
'ShowLocaleClass:read {"locale":"fr"}',
1828
'ShowLocaleFn:read {"locale":"fr"}',
1829
]);
@@ -1895,6 +1895,9 @@ describe('ReactIncremental', () => {
1895
statefulInst.setState({locale: 'gr'});
1896
ReactNoop.flush();
1897
expect(ops).toEqual([
1898
+ // Intl is below setState() so it might have been
1899
+ // affected by it. Therefore we re-render and recompute
1900
+ // its child context.
1901
'Intl:read null',
1902
'Intl:provide {"locale":"gr"}',
1903
'ShowLocaleClass:read {"locale":"gr"}',