shouldcomponentupdate react helmet example create component app performance reactjs isomorphic-javascript render-to-string react-dom

performance - react - shouldcomponentupdate example



React renderToString() Componentes de rendimiento y almacenamiento en caché React (4)

Idea 1: componentes de almacenamiento en caché

Actualización 1 : he agregado un ejemplo de trabajo completo en la parte inferior. data-reactid en caché los componentes en la memoria y actualiza los data-reactid .

En realidad, esto se puede hacer fácilmente. Debe monkey-patch ReactCompositeComponent y buscar una versión en caché:

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) }

Debe hacer esto antes de require(''react'') en cualquier lugar de su aplicación.

Nota del paquete web: si usa algo como un new webpack.ProvidePlugin({''React'': ''react''}) debe cambiarlo a new webpack.ProvidePlugin({''React'': ''react-override''}) en react-override.js y export react (es decir, module.exports = require(''react'') )

Un ejemplo completo que almacena en caché en la memoria y actualiza el atributo reactid podría ser este:

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'');

Me di cuenta de que el método reactDOM.renderToString() comienza a ralentizarse significativamente al representar un árbol de componentes grande en el servidor.

Antecedentes

Un poco de trasfondo. El sistema es una pila completamente isomorfa. El componente de App nivel más alto representa plantillas, páginas, elementos dom y más componentes. Al mirar el código de reacción, descubrí que representa ~ 1500 componentes (esto incluye cualquier etiqueta dom simple que se trate como un componente simple, <p>this is a react component</p> .

En desarrollo, renderizar ~ 1500 componentes requiere ~ 200-300ms. Al eliminar algunos componentes pude obtener ~ 1200 componentes para renderizar en ~ 175-225 ms.

En producción, renderToString en ~ 1500 componentes toma alrededor de ~ 50-200ms.

El tiempo parece ser lineal. Ningún componente es lento, sino que es la suma de muchos.

Problema

Esto crea algunos problemas en el servidor. El largo método da como resultado largos tiempos de respuesta del servidor. El TTFB es mucho más alto de lo que debería ser. Con llamadas de API y lógica de negocios, la respuesta debería ser de 250 ms, pero con un renderToString de 250 ms se duplica. Malo para SEO y usuarios. Además, al ser un método sincrónico, renderToString() puede bloquear el servidor de nodos y respaldar las solicitudes posteriores (esto podría resolverse utilizando 2 servidores de nodos separados: 1 como servidor web y 1 como un servicio para procesar únicamente la reacción).

Intentos

Idealmente, tomaría 5-50 ms para renderToString en producción. He estado trabajando en algunas ideas, pero no estoy exactamente seguro de cuál sería el mejor enfoque.

Idea 1: componentes de almacenamiento en caché

Cualquier componente que esté marcado como ''estático'' podría almacenarse en caché. Al mantener un caché con el marcado renderizado, renderToString() podría verificar el caché antes de renderizar. Si encuentra un componente, automáticamente agarra la cadena. Hacer esto en un componente de alto nivel ahorraría el montaje de todos los componentes secundarios anidados. Tendría que reemplazar el rootID de reacción del marcado del componente en caché con el rootID actual.

Idea 2: Marcar componentes como simples / tontos

Al definir un componente como ''simple'', react debe poder omitir todos los métodos del ciclo de vida al renderizar. Reaccionar ya hace esto para los componentes principales del núcleo de reacción ( <p/> , <h1/> , etc.). Sería bueno extender componentes personalizados para usar la misma optimización.

Idea 3: omitir componentes en el renderizado del lado del servidor

Los componentes que no necesitan ser devueltos por el servidor (sin valor de SEO) simplemente se pueden omitir en el servidor. Una vez que se carga el cliente, establezca un indicador clientLoaded en true y páselo para imponer una nueva representación.

Cierre y otros intentos

La única solución que he implementado hasta ahora es reducir la cantidad de componentes que se representan en el servidor.

Algunos proyectos que estamos analizando incluyen:

¿Alguien ha enfrentado problemas similares? ¿Qué has podido hacer? Gracias.


Creo que fast-react-render puede ayudarte. Aumenta el rendimiento de su servidor renderizando tres veces.

Para probarlo, solo necesita instalar el paquete y reemplazar ReactDOM.renderToString a FastReactRender.elementToString:

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

También puede usar el fast-react-server , en ese caso el renderizado será 14 veces más rápido que el renderizado de reacción tradicional. Pero para eso, cada componente, que desea representar, debe declararse con él (vea un ejemplo en fast-react-seed, cómo puede hacerlo para webpack).


No es una solución completa. Tuve el mismo problema, con mi aplicación isomorphic react, y usé un par de cosas.

1) Use Nginx frente a su servidor nodejs y guarde en caché la respuesta renderizada por un corto tiempo.

2) En caso de mostrar una lista de elementos, solo utilizo un subconjunto de la lista. por ejemplo, renderizaré solo X elementos para llenar la ventana gráfica y cargaré el resto de la lista en el lado del cliente usando Websocket o XHR.

3) Algunos de mis componentes están vacíos en el procesamiento del lado del servidor y solo se cargarán desde el código del lado del cliente (componentDidMount). Estos componentes suelen ser gráficos o componentes relacionados con el perfil. esos componentes generalmente no tienen ningún beneficio en el punto de vista de SEO

4) Sobre SEO, desde mi experiencia de 6 meses con una aplicación isomorfa. Google Bot puede leer la página web React del lado del cliente fácilmente, por lo que no estoy seguro de por qué nos molestamos con la representación del lado del servidor.

5) Mantenga <Head > y <Footer> como una cadena estática o use un motor de plantillas ( Reactjs-handellbars ), y renderice solo el contenido de la página (debería guardar algunos componentes renderizados). En el caso de una aplicación de una sola página, puede actualizar la descripción del título en cada navegación dentro de Router.Run .


Usando react-router1.0 y react0.14, serializamos por error nuestro objeto de flujo varias veces.

RoutingContext llamará a createElement para cada plantilla en sus rutas de enrutador de reacción. Esto le permite inyectar los accesorios que desee. También usamos flux. Enviamos una versión serializada de un objeto grande. En nuestro caso, estábamos haciendo flux.serialize() dentro de createElement. El método de serialización podría tardar unos 20 ms. ¡Con 4 plantillas, eso sería 80ms extra para su método renderToString() !

Código antiguo

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);

Fácilmente optimizado para esto:

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);

En mi caso, esto ayudó a reducir el tiempo de renderToString() de ~ 120ms a ~ 30ms. (Todavía necesita agregar el 1x serialize() ''s ~ 20ms al total, lo que sucede antes de renderToString() ) Fue una mejora rápida y agradable. - ¡Es importante recordar hacer siempre las cosas correctamente, incluso si no conoce el impacto inmediato!