From 1c0708966d6cdf365cb9f3043c301921b246ef21 Mon Sep 17 00:00:00 2001 From: Alex Taylor Date: Mon, 15 Oct 2018 14:41:14 -0700 Subject: [PATCH 1/2] Add support for React.pure in ReactDOMServer --- ...ctDOMServerIntegrationSpecialTypes-test.js | 72 ++++++++++++++++++- .../src/server/ReactPartialRenderer.js | 29 ++++++++ 2 files changed, 100 insertions(+), 1 deletion(-) diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationSpecialTypes-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationSpecialTypes-test.js index 4f717c7a48d..71d062a4589 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationSpecialTypes-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationSpecialTypes-test.js @@ -14,6 +14,11 @@ const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegratio let React; let ReactDOM; let ReactDOMServer; +let forwardRef; +let pure; +let yieldedValues; +let yieldValue; +let clearYields; function initModules() { // Reset warning cache. @@ -21,6 +26,18 @@ function initModules() { React = require('react'); ReactDOM = require('react-dom'); ReactDOMServer = require('react-dom/server'); + forwardRef = React.forwardRef; + pure = React.pure; + + yieldedValues = []; + yieldValue = value => { + yieldedValues.push(value); + }; + clearYields = () => { + const ret = yieldedValues; + yieldedValues = []; + return ret; + }; // Make them available to the helpers. return { @@ -40,7 +57,7 @@ describe('ReactDOMServerIntegration', () => { const FunctionComponent = ({label, forwardedRef}) => (
{label}
); - const WrappedFunctionComponent = React.forwardRef((props, ref) => ( + const WrappedFunctionComponent = forwardRef((props, ref) => ( )); @@ -65,4 +82,57 @@ describe('ReactDOMServerIntegration', () => { expect(div.tagName).toBe('DIV'); expect(div.textContent).toBe('Test'); }); + + describe('pure functional components', () => { + beforeEach(() => { + resetModules(); + }); + + function Text({text}) { + yieldValue(text); + return {text}; + } + + function Counter({count}) { + return ; + } + + itRenders('basic render', async render => { + const PureCounter = pure(Counter); + const domNode = await render(); + expect(domNode.textContent).toEqual('Count: 0'); + }); + + itRenders('composition with forwardRef', async render => { + const RefCounter = (props, ref) => ; + const PureRefCounter = pure(forwardRef(RefCounter)); + + const ref = React.createRef(); + ref.current = 0; + await render(); + + expect(clearYields()).toEqual(['Count: 0']); + }); + + itRenders('with comparator', async render => { + const PureCounter = pure(Counter, (oldProps, newProps) => false); + await render(); + expect(clearYields()).toEqual(['Count: 0']); + }); + + itRenders( + 'comparator functions are not invoked on the server', + async render => { + const PureCounter = React.pure(Counter, (oldProps, newProps) => { + yieldValue( + `Old count: ${oldProps.count}, New count: ${newProps.count}`, + ); + return oldProps.count === newProps.count; + }); + + await render(); + expect(clearYields()).toEqual(['Count: 0']); + }, + ); + }); }); diff --git a/packages/react-dom/src/server/ReactPartialRenderer.js b/packages/react-dom/src/server/ReactPartialRenderer.js index c8fc0ea147e..18276c8ec37 100644 --- a/packages/react-dom/src/server/ReactPartialRenderer.js +++ b/packages/react-dom/src/server/ReactPartialRenderer.js @@ -39,6 +39,7 @@ import { REACT_PROVIDER_TYPE, REACT_CONTEXT_TYPE, REACT_LAZY_TYPE, + REACT_PURE_TYPE, } from 'shared/ReactSymbols'; import { @@ -1001,6 +1002,34 @@ class ReactDOMServerRenderer { this.stack.push(frame); return ''; } + case REACT_PURE_TYPE: { + const element: ReactElement = ((nextChild: any): ReactElement); + let nextChildren; + if ( + typeof elementType.render === 'object' && elementType.render !== null && elementType.render.$$typeof === REACT_FORWARD_REF_TYPE + ) { + nextChildren = toArray( + elementType.render.render(element.props, element.ref), + ); + } else { + nextChildren = toArray( + elementType.render(element.props), + ); + } + const frame: Frame = { + type: null, + domNamespace: parentNamespace, + children: nextChildren, + childIndex: 0, + context: context, + footer: '', + }; + if (__DEV__) { + ((frame: any): FrameDev).debugElementStack = []; + } + this.stack.push(frame); + return ''; + } case REACT_PROVIDER_TYPE: { const provider: ReactProvider = (nextChild: any); const nextProps = provider.props; From 28f5c3bc8eb78192d3811f0c535b6a0200894b6d Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Sat, 20 Oct 2018 00:27:44 -0700 Subject: [PATCH 2/2] Unwrap pure wrappers by creating an additional element as a single child This is very slow but meh. We're rewriting this whole thing anyway. --- .../src/server/ReactPartialRenderer.js | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/packages/react-dom/src/server/ReactPartialRenderer.js b/packages/react-dom/src/server/ReactPartialRenderer.js index 18276c8ec37..c45e5694528 100644 --- a/packages/react-dom/src/server/ReactPartialRenderer.js +++ b/packages/react-dom/src/server/ReactPartialRenderer.js @@ -1004,18 +1004,12 @@ class ReactDOMServerRenderer { } case REACT_PURE_TYPE: { const element: ReactElement = ((nextChild: any): ReactElement); - let nextChildren; - if ( - typeof elementType.render === 'object' && elementType.render !== null && elementType.render.$$typeof === REACT_FORWARD_REF_TYPE - ) { - nextChildren = toArray( - elementType.render.render(element.props, element.ref), - ); - } else { - nextChildren = toArray( - elementType.render(element.props), - ); - } + let nextChildren = [ + React.createElement( + elementType.type, + Object.assign({ref: element.ref}, element.props), + ), + ]; const frame: Frame = { type: null, domNamespace: parentNamespace,