programing

React renderToString () 성능 및 React 컴포넌트 캐싱

nasanasas 2020. 12. 13. 09:43
반응형

React renderToString () 성능 및 React 컴포넌트 캐싱


reactDOM.renderToString()서버에서 큰 구성 요소 트리를 렌더링 할 때 메서드가 크게 느려지기 시작 한다는 것을 알았습니다 .

배경

약간의 배경. 시스템은 완전히 동형 스택입니다. 가장 높은 수준의 App구성 요소는 템플릿, 페이지, DOM 요소 및 기타 구성 요소를 렌더링합니다. 반응 코드를 살펴보면 ~ 1500 개의 구성 요소를 렌더링하는 것으로 나타났습니다 (단순 구성 요소로 처리되는 모든 간단한 dom 태그 포함, <p>this is a react component</p>.

개발시 ~ 1500 개의 구성 요소를 렌더링하는 데 ~ 200-300ms가 걸립니다. 일부 구성 요소를 제거하여 ~ 175-225ms에서 렌더링 할 ~ 1200 개의 구성 요소를 얻을 수있었습니다.

프로덕션에서 ~ 1500 개 구성 요소의 renderToString은 약 50 ~ 200ms가 걸립니다.

시간은 선형적인 것처럼 보입니다. 하나의 구성 요소가 느리지 않고 오히려 많은 요소의 합계입니다.

문제

이로 인해 서버에 몇 가지 문제가 발생합니다. 방법이 길면 서버 응답 시간이 길어집니다. TTFB는 예상보다 훨씬 높습니다. API 호출 및 비즈니스 로직을 사용하면 응답이 250ms 여야하지만 250ms renderToString을 사용하면 두 배가됩니다! SEO 및 사용자에게 좋지 않습니다. 또한 동기식 방식이므로 renderToString()노드 서버를 차단하고 후속 요청을 백업 할 수 있습니다 (이는 2 개의 개별 노드 서버를 사용하여 해결할 수 있습니다. 1 개는 웹 서버로, 1 개는 반응 만 렌더링하는 서비스로 사용).

시도

이상적으로는 프로덕션에서 renderToString에 5-50ms가 걸립니다. 몇 가지 아이디어를 작업 해 왔지만 최선의 접근 방식이 무엇인지 정확히 모르겠습니다.

아이디어 1 : 캐싱 구성 요소

'정적'으로 표시된 모든 구성 요소는 캐시 될 수 있습니다. 렌더링 된 마크 업이있는 캐시를 유지함으로써는 렌더링 renderToString()전에 캐시를 확인할 수 있습니다. 구성 요소를 찾으면 자동으로 문자열을 가져옵니다. 높은 수준의 구성 요소에서이 작업을 수행하면 중첩 된 모든 하위 구성 요소의 마운팅이 저장됩니다. 캐시 된 구성 요소 마크 업의 반응 rootID를 현재 rootID로 바꿔야합니다.

아이디어 2 : 구성 요소를 단순 / 멍청한 것으로 표시

컴포넌트를 '단순'으로 정의함으로써 react는 렌더링 할 때 모든 라이프 사이클 메소드를 건너 뛸 수 있어야합니다. 이미 코어에 대해이 작업을 수행 반응은 DOM 요소를 (반응 <p/>, <h1/>등). 동일한 최적화를 사용하도록 사용자 지정 구성 요소를 확장하는 것이 좋습니다.

아이디어 3 : 서버 측 렌더링에서 구성 요소 건너 뛰기

서버에서 반환 할 필요가없는 구성 요소 (SEO 값 없음)는 서버에서 간단히 건너 뛸 수 있습니다. 클라이언트가로드되면에 clientLoaded플래그를 설정 true하고 전달하여 다시 렌더링합니다.

결산 및 기타 시도

지금까지 구현 한 유일한 솔루션은 서버에서 렌더링되는 구성 요소의 수를 줄이는 것입니다.

우리가보고있는 일부 프로젝트는 다음과 같습니다.

비슷한 문제에 직면 한 사람이 있습니까? 무엇을 할 수 있었습니까? 감사.


react-router1.0 및 react0.14를 사용하여 실수로 flux 객체를 여러 번 직렬화했습니다.

RoutingContextcreateElementreact-router 경로의 모든 템플릿을 호출 합니다. 이를 통해 원하는 소품을 주입 할 수 있습니다. 우리는 또한 플럭스를 사용합니다. 대형 객체의 직렬화 된 버전을 보냅니다. 우리의 경우에는 flux.serialize()createElement 내에서 수행했습니다 . 직렬화 방법은 ~ 20ms가 걸릴 수 있습니다. 4 개의 템플릿을 사용하면 renderToString()방법에 추가로 80ms가 소요됩니다 !

이전 코드 :

function createElement(Component, props) {
    props = _.extend(props, {
        flux: flux,
        path: path,
        serializedFlux: flux.serialize();
    });
    return <Component {...props} />;
}
var start = Date.now();
markup = renderToString(<RoutingContext {...renderProps} createElement={createElement} />);
console.log(Date.now() - start);

이에 쉽게 최적화 :

var serializedFlux = flux.serialize(); // serialize one time only!

function createElement(Component, props) {
    props = _.extend(props, {
        flux: flux,
        path: path,
        serializedFlux: serializedFlux
    });
    return <Component {...props} />;
}
var start = Date.now();
markup = renderToString(<RoutingContext {...renderProps} createElement={createElement} />);
console.log(Date.now() - start);

제 경우에는 이것이 renderToString()~ 120ms에서 ~ 30ms로 시간을 줄이는 데 도움이되었습니다 . (당신은 여전히 ​​1x serialize()의 ~ 20ms를 총계 에 추가해야합니다. renderToString()이것은) 아주 빠른 개선이었습니다. -즉각적인 영향을 알지 못하더라도 항상 올바르게 일하는 것을 기억하는 것이 중요합니다!


아이디어 1 : 캐싱 구성 요소

업데이트 1 : 하단에 완전한 작업 예제를 추가했습니다. 메모리 및 업데이트에 구성 요소를 캐시합니다 data-reactid.

이것은 실제로 쉽게 할 수 있습니다. 원숭이 패치를 적용 ReactCompositeComponent 하고 캐시 된 버전을 확인 해야합니다 .

import ReactCompositeComponent from 'react/lib/ReactCompositeComponent';
const originalMountComponent = ReactCompositeComponent.Mixin.mountComponent;
ReactCompositeComponent.Mixin.mountComponent = function() {
    if (hasCachedVersion(this)) return cache;
    return originalMountComponent.apply(this, arguments)
}

당신은 당신이하기 전에이 작업을 수행해야 require('react')앱에 어디.

웹팩 참고 : 처럼 당신이 뭔가를 사용하는 경우 new webpack.ProvidePlugin({'React': 'react'})당신이 그것을 변경해야 new webpack.ProvidePlugin({'React': 'react-override'})당신이 당신의 수정을 할 경우 react-override.js수출 react(예 module.exports = require('react'))

메모리에 캐시하고 reactid속성을 업데이트하는 완전한 예는 다음과 같습니다.

import ReactCompositeComponent from 'react/lib/ReactCompositeComponent';
import jsan from 'jsan';
import Logo from './logo.svg';

const cachable = [Logo];
const cache = {};

function splitMarkup(markup) {
    var markupParts = [];
    var reactIdPos = -1;
    var endPos, startPos = 0;
    while ((reactIdPos = markup.indexOf('reactid="', reactIdPos + 1)) != -1) {
        endPos = reactIdPos + 9;
        markupParts.push(markup.substring(startPos, endPos))
        startPos = markup.indexOf('"', endPos);
    }
    markupParts.push(markup.substring(startPos))
    return markupParts;
}

function refreshMarkup(markup, hostContainerInfo) {
    var refreshedMarkup = '';
    var reactid;
    var reactIdSlotCount = markup.length - 1;
    for (var i = 0; i <= reactIdSlotCount; i++) {
        reactid = i != reactIdSlotCount ? hostContainerInfo._idCounter++ : '';
        refreshedMarkup += markup[i] + reactid
    }
    return refreshedMarkup;
}

const originalMountComponent = ReactCompositeComponent.Mixin.mountComponent;
ReactCompositeComponent.Mixin.mountComponent = function (renderedElement, hostParent, hostContainerInfo, transaction, context) {
    return originalMountComponent.apply(this, arguments);
    var el = this._currentElement;
    var elType = el.type;
    var markup;
    if (cachable.indexOf(elType) > -1) {
        var publicProps = el.props;
        var id = elType.name + ':' + jsan.stringify(publicProps);
        markup = cache[id];
        if (markup) {
            return refreshMarkup(markup, hostContainerInfo)
        } else {
            markup = originalMountComponent.apply(this, arguments);
            cache[id] = splitMarkup(markup);
        }
    } else {
        markup = originalMountComponent.apply(this, arguments)
    }
    return markup;
}
module.exports = require('react');

완전하지 않은 해결책은 내 반응 동형 앱과 같은 문제가 있었고 몇 가지를 사용했습니다.

1) nodejs 서버 앞에서 Nginx를 사용하고 렌더링 된 응답을 잠시 캐시합니다.

2) In Case of showing a list of items , i uses a only a subset of list . for example i will render only X items to fill up the viewport , and load the rest of the list in the client side using Websocket or XHR.

3) Some of my components are empty in serverside rendering and will only load from client side code (componentDidMount). These components are usually , graphs or profile related components. those components usually doesn't have any benefit in SEO point of view

4) About SEO , from my experience 6 Month with an isomorphic app. Google Bot can read Client side React Web page easily , so I'm not sure why we bother with the server side rendering.

5)Keep the <Head >and <Footer> as static string or use template engine (Reactjs-handellbars) , and render only the content of the page, ( it should save a few renderd components). In case of a single page app, you can update the the title description in each navigation inside Router.Run.


I think fast-react-render can help you. It increases the performance of your server rendering three times.

For try it, you only need to install package and replace ReactDOM.renderToString to FastReactRender.elementToString:

var ReactRender = require('fast-react-render');

var element = React.createElement(Component, {property: 'value'});
console.log(ReactRender.elementToString(element, {context: {}}));

Also you can use fast-react-server, in that case render will be 14 times as fast as traditional react rendering. But for that each component, which you want to render, must be declared with it (see an example in fast-react-seed, how you can do it for webpack).

참고URL : https://stackoverflow.com/questions/34728962/react-rendertostring-performance-and-caching-react-components

반응형