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:
- https://github.com/aickin/react-dom-stream (todavía trabajando en implementar esto para una prueba)
- https://babeljs.io/docs/plugins/transform-react-inline-elements/ (parece que esto está en la línea de la Idea 2)
¿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!