Подтвердить что ты не робот

Реагировать renderToString() Производительность и кэширование реагирующих компонентов

Я заметил, что метод reactDOM.renderToString() начинает значительно замедляться при рендеринге большого дерева компонентов на сервере.

Фон

Немного предыстории. Система является полностью изоморфным стеком. Компонент высшего уровня App отображает шаблоны, страницы, элементы dom и другие компоненты. Посмотрев код реакции, я обнаружил, что он рендерит ~ 1500 компонентов (это относится к любому простому тегу dom, который рассматривается как простой компонент, <p>this is a react component</p>.

В разработке рендеринг ~ 1500 компонентов занимает ~ 200-300мс. Удалив некоторые компоненты, я смог получить ~ 1200 компонентов для визуализации за ~ 175-225 мс.

В производстве renderToString для ~ 1500 компонентов занимает около 50-200 мс.

Время кажется линейным. Ни один из компонентов не является медленным, скорее это сумма многих.

Проблема

Это создает некоторые проблемы на сервере. Длительный метод приводит к увеличению времени ответа сервера. TTFB намного выше, чем должен быть. При вызовах API и бизнес-логике ответ должен составлять 250 мс, а при рендеринге 250 мс он удваивается! Плохо для SEO и пользователей. Кроме того, будучи синхронным методом, renderToString() может блокировать сервер узлов и создавать резервные копии последующих запросов (это можно решить, используя 2 отдельных сервера узлов: 1 в качестве веб-сервера и 1 в качестве службы, предназначенной исключительно для реагирования на рендеринг).

Попытки

В идеале для рендеринга ToString в производстве потребуется 5-50 мс. Я работал над некоторыми идеями, но я не совсем уверен, какой будет лучший подход.

Идея 1: Кэширование компонентов

Любой компонент, помеченный как "статический", может быть кэширован. Сохраняя кэш с визуализированной разметкой, renderToString() может проверять кэш перед визуализацией. Если он находит компонент, он автоматически захватывает строку. Выполнение этого на компоненте высокого уровня сохранит монтирование всех вложенных дочерних компонентов. Вам нужно заменить rootID реагирующей разметки компонента rootID на текущий rootID.

Идея 2: Маркировка компонентов как простых/немых

Определяя компонент как "простой", реакция должна пропускать все методы жизненного цикла при рендеринге. React уже делает это для основных компонентов реагирования (<p/>, <h1/> и т.д.). Было бы неплохо расширить пользовательские компоненты для использования той же оптимизации.

Идея 3: Пропустить компоненты при рендеринге на стороне сервера

Компоненты, которые не должны быть возвращены сервером (без значения SEO), могут быть просто пропущены на сервере. Как только клиент загрузится, установите флаг clientLoaded на true и передайте его вниз для принудительного повторного рендеринга.

Закрытие и другие попытки

Единственное решение, которое я реализовал до сих пор, - это уменьшить количество компонентов, отображаемых на сервере.

Вот некоторые проекты, на которые мы смотрим:

Кто-нибудь сталкивался с подобными проблемами? Что ты смог сделать? Благодарю.

4b9b3361

Ответ 1

Используя response-router1.0 и react0.14, мы ошибочно сериализуем наш объект потока несколько раз.

RoutingContext будет вызывать createElement для каждого шаблона в маршрутах вашего реагирования. Это позволяет вам вводить любые реквизиты, которые вы хотите. Мы также используем поток. Мы отправляем сериализованную версию большого объекта. В нашем случае мы делали flux.serialize() внутри createElement. Метод сериализации может занимать ~ 20 мс. С 4 шаблонами это будет дополнительный 80 мс для вашего метода renderToString()!

Старый код:

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() от ~ 120 мс до ~ 30 мс. (Вам все равно нужно добавить 1x serialize() ~ 20ms к общей сумме, что происходит до renderToString()). Это было хорошее быстрое улучшение. - Важно помнить, что всегда делайте все правильно, даже если вы не знаете непосредственного воздействия!

Ответ 2

Идея 1: Кэширование компонентов

Обновление 1: я добавил полный рабочий пример внизу. Он кэширует компоненты в памяти и обновляет data-reactid.

Это на самом деле можно сделать легко. Вам следует monkey-patch 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');

Ответ 3

Это не полное решение У меня была та же проблема с моим изоморфным приложением реагировать, и я использовал несколько вещей.

1) Используйте Nginx перед вашим сервером nodejs и короткое время кешируйте полученный ответ.

2) В случае отображения списка элементов я использую только подмножество списка. например, я буду отображать только элементы X, чтобы заполнить область просмотра, и загрузить остальную часть списка на стороне клиента, используя Websocket или XHR.

3) Некоторые из моих компонентов пусты при рендеринге на стороне сервера и будут загружаться только из кода на стороне клиента (componentDidMount). Этими компонентами обычно являются графики или компоненты, связанные с профилем. эти компоненты обычно не приносят никакой пользы с точки зрения SEO

4) О SEO, из моего опыта 6 месяцев с изоморфным приложением. Google Bot может легко читать веб-страницу React на стороне клиента, поэтому я не уверен, почему мы беспокоимся о рендеринге на стороне сервера.

5) Сохраните <Head > и <Footer> как статическую строку или используйте шаблонизатор (Reactjs-handellbars) и визуализируйте только содержимое страницы (в нем должны быть сохранены несколько визуализированных компонентов). В случае одностраничного приложения вы можете обновить описание заголовка в каждой навигации внутри Router.Run.

Ответ 4

Я думаю, fast-react-render может вам помочь. Это увеличивает производительность рендеринга вашего сервера три раза.

Для этого вам нужно всего лишь установить пакет и заменить ReactDOM.renderToString на FastReactRender.elementToString:

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

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

Также вы можете использовать fast-react-server, в этом случае рендер будет в 14 раз быстрее, чем традиционный рендеринг. Но для этого каждый компонент, который вы хотите визуализировать, должен быть объявлен вместе с ним (см. Пример в quick-react-seed, как вы можете это сделать для webpack).