@samitouri / QOS-React / commits / a6ee5b876a

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"}',