Cómo hacer que una aplicación React sea más rápida con el cohospedaje estatal

El artículo trata sobre la colocación estatal , es decir, sobre la coubicación de estados , este término también podría traducirse como colocación estatal o colocación estatal .


Una de las principales razones para ralentizar una aplicación React es su estado global. Mostraré esto con un ejemplo de una aplicación muy simple, después de lo cual daré un ejemplo más cercano a la vida real.


Aquí hay una aplicación simple en la que puede ingresar un nombre para el perro (si la ventana no funciona, aquí está el enlace ):



Si juegas con esta aplicación, pronto descubrirás que funciona muy lentamente. Al interactuar con cualquier campo de entrada, hay problemas notables de rendimiento. En tal situación, podría usar un React.memo salvavidas en forma de React.memo y envolverlo con todos los componentes con un renderizado lento. Pero tratemos de resolver este problema de manera diferente.


Aquí está el código para esta aplicación:


 function sleep(time) { const done = Date.now() + time while (done > Date.now()) { // ... } } //      // -       function SlowComponent({time, onChange}) { sleep(time) return ( <div> Wow, that was{' '} <input value={time} type="number" onChange={e => onChange(Number(e.target.value))} /> ms slow </div> ) } function DogName({time, dog, onChange}) { return ( <div> <label htmlFor="dog">Dog Name</label> <br /> <input id="dog" value={dog} onChange={e => onChange(e.target.value)} /> <p> {dog ? `${dog}'s favorite number is ${time}.` : 'enter a dog name'} </p> </div> ) } function App() { //   " " (global state) const [dog, setDog] = React.useState('') const [time, setTime] = React.useState(200) return ( <div> <DogName time={time} dog={dog} onChange={setDog} /> <SlowComponent time={time} onChange={setTime} /> </div> ) } 

Si no ha leído el artículo sobre Colocación (coubicación), le sugiero que lo lea. Sabiendo que el cohospedaje puede facilitar el trabajo con nuestra aplicación, usemos este principio cuando trabajemos con estados.


Preste atención al código de nuestra aplicación, es decir, el estado de time : es utilizado por cada componente de nuestra aplicación, por lo tanto, se levantó ( levantando, elevando el estado ) al componente de la App (el componente que envuelve toda nuestra aplicación). Sin embargo, el estado "dog" ( dog y setDog ) solo lo utiliza un componente, no es necesario en el componente de la App , así que vamos al componente DogName :


 function DogName({time}) { // <-    const [dog, setDog] = React.useState('') // <-   return ( <div> <label htmlFor="dog">Dog Name</label> <br /> <input id="dog" value={dog} onChange={e => setDog(e.target.value)} /> <p> {dog ? `${dog}'s favorite number is ${time}.` : 'enter a dog name'} </p> </div> ) } function App() { //   " " (global state) const [time, setTime] = React.useState(200) return ( <div> <DogName time={time} /> // <-    <SlowComponent time={time} onChange={setTime} /> </div> ) } 

Y aquí está nuestro resultado (si la ventana no funciona, enlace ):



Wow! Introducir un nombre ahora funciona MUCHO más rápido. Además, el componente se ha vuelto más fácil de mantener gracias a la ubicación conjunta. Pero, ¿por qué funcionó más rápido?


Dicen que la mejor manera de hacer algo rápidamente es hacer la menor cantidad de cosas posible. Esto es exactamente lo que está sucediendo aquí. Cuando gestionamos el estado ubicado en la parte superior del árbol de componentes, cada actualización de este estado invalidará todo el árbol. La reacción no sabe qué ha cambiado, por eso, tiene que verificar todos los componentes para ver si necesitan actualizaciones DOM. Este proceso no es gratuito y consume recursos (especialmente si tiene componentes intencionalmente lentos). Pero si mueve el estado lo más bajo posible en el árbol de componentes, como hicimos con el estado del dog y el componente DogName , entonces React hará menos comprobaciones. React no verificará el componente SlowComponent (que hicimos deliberadamente lento), ya que React sabe que este componente todavía no puede afectar la salida.


En resumen, antes, al cambiar el nombre del perro, cada componente se verificaba para cambios (re-renderizado). Y después de los cambios que hicimos en el código, React comenzó a verificar solo el componente DogName . ¡Esto ha llevado a un marcado aumento en la productividad!


En la vida real


Veo que los desarrolladores están poniendo en el repositorio global de Redux o en el contexto global lo que realmente no debería ser global. DogName como el DogName del ejemplo anterior a menudo son lugares donde se produce un problema de rendimiento. A menudo veo que este problema se manifiesta al interactuar con el mouse (por ejemplo, al mostrar información sobre herramientas sobre un gráfico o sobre una tabla de datos).


Una solución a este problema es "cancelar" la interacción del usuario (es decir, esperamos hasta que el usuario deje de escribir y luego aplicamos la actualización de estado). A veces, esto es lo mejor que podemos hacer, pero puede conducir a una mala experiencia del usuario (el modo concurrente futuro debería minimizar la necesidad de hacerlo. Vea esta demostración de Dan Abramov ).


Otra solución que los desarrolladores usan a menudo es usar una de las representaciones de rescate de React, por ejemplo React.memo . Esto funcionará muy bien en nuestro ejemplo descabellado, ya que le permite a React omitir la representación del SlowComponent , pero en la práctica la aplicación puede sufrir debido a la "muerte por mil cortes", ya que en una aplicación real la desaceleración generalmente no se debe a una lenta componente, y debido al trabajo insuficientemente rápido de muchos componentes, por lo que debe usar React.memo todas partes. Una vez hecho esto, deberá comenzar a usar useMemo y useCallback , de lo contrario, todo el trabajo que React.memo en React.memo será en vano. Estas acciones pueden resolver el problema, pero aumentan significativamente la complejidad de su código y, de hecho, aún son menos efectivas que compartir estados, ya que React necesita pasar por cada componente (comenzando desde la parte superior) para determinar si necesita procesarse nuevamente.


Si quieres jugar con un ejemplo un poco menos descabellado, ve a codesandbox aquí .


¿Qué es el uso compartido?


El principio de colocación conjunta establece:


El código debe ubicarse lo más cerca posible del lugar al que se refiere.

Entonces, para cumplir con este principio, nuestro estado de dog debe estar dentro del componente DogName :


 function DogName({time}) { const [dog, setDog] = React.useState('') return ( <div> <label htmlFor="dog">Dog Name</label> <br /> <input id="dog" value={dog} onChange={e => setDog(e.target.value)} /> <p> {dog ? `${dog}'s favorite number is ${time}.` : 'enter a dog name'} </p> </div> ) } 

Pero, ¿qué pasa si dividimos este componente en varios componentes? ¿A dónde debe ir el estado? La respuesta es la misma: "lo más cerca posible del lugar con el que se relaciona", y ese será el componente principal común más cercano . Como ejemplo, analicemos el componente DogName que la input p muestren en diferentes componentes:


 function DogName({time}) { const [dog, setDog] = React.useState('') return ( <div> <DogInput dog={dog} onChange={setDog} /> <DogFavoriteNumberDisplay time={time} dog={dog} /> </div> ) } function DogInput({dog, onChange}) { return ( <> <label htmlFor="dog">Dog Name</label> <br /> <input id="dog" value={dog} onChange={e => onChange(e.target.value)} /> </> ) } function DogFavoriteNumberDisplay({time, dog}) { return ( <p> {dog ? `${dog}'s favorite number is ${time}.` : 'enter a dog name'} </p> ) } 

No podemos mover el estado al componente DogInput , porque el componente DogFavoriteNumberDisplay también necesita acceso al estado, por lo que nos movemos desde la parte inferior hasta la parte superior del árbol de componentes hasta que encontremos el padre común de estos dos componentes y organicemos la administración del estado en él.


Todo esto se aplica a situaciones en las que necesita controlar el estado de docenas de componentes en una pantalla particular de su aplicación. Incluso puede mover esto al contexto para evitar la perforación del puntal si lo desea. Pero mantenga este contexto lo más cerca posible del lugar al que pertenece, y luego continuará manteniendo el buen rendimiento (y usabilidad del código) que proporciona el uso compartido. Recuerde que no necesita colocar todos los contextos en el nivel superior de su aplicación React. Manténgalos donde tenga más sentido.


Esta es la idea principal de mi otra publicación, Application State Management with React . Mantenga sus estados lo más cerca posible del lugar donde se usan, esto mejorará tanto el rendimiento como la usabilidad del código. Con este enfoque, lo único que posiblemente empeorará el rendimiento de su aplicación son las interacciones especialmente complejas con los elementos de la interfaz.


Entonces, ¿qué usar, contextos o Redux?


Si lees "Un simple truco para optimizar las representaciones de React" , entonces sabes que puedes asegurarte de que solo se actualizan aquellos componentes que usan el estado modificado. De esta manera, puede solucionar este problema. Pero las personas aún enfrentan problemas de rendimiento cuando usan el Editor. El problema es que React-Redux espera que sigas sus recomendaciones para evitar la representación innecesaria de los componentes conectados . Siempre hay una posibilidad de error; puede configurar accidentalmente componentes para que comiencen a reproducirse con demasiada frecuencia cuando cambian otros estados globales. Y cuanto mayor sea su aplicación, más negativo será el efecto de esto, especialmente si agrega demasiados estados al Editor.


Existen métodos que pueden ayudarlo a evitar estos problemas, por ejemplo, use memoized Reselect mapState o lea la documentación del Editor para obtener más información sobre memoized Reselect mapState mejorar el rendimiento de la aplicación .


Vale la pena señalar que la colocación conjunta se puede aplicar junto con el Editor. Solo use Editores solo para estados globales, y para todo lo demás, use la ubicación conjunta. Hay algunas reglas útiles en las preguntas frecuentes del editor para ayudarlo a decidir si el estado debería funcionar en el editor o si debería permanecer en el componente .


Por cierto, si divide su estado en dominios (usando varios contextos dependiendo del dominio), entonces el problema será menos pronunciado.


Pero el hecho sigue siendo: la ubicación conjunta de los estados reduce los problemas de rendimiento y simplifica el mantenimiento del código.


Decide dónde poner el estado


Árbol de decisión:


dónde poner


Versión de texto, si no hay forma de mirar la imagen:


  • 1 Comenzamos el desarrollo de la aplicación. Ir a 2
  • 2 Estado en el componente. Ir a 3
  • 3 ¿La condición es utilizada solo por este componente?
    • ¿Eh? Vamos a 4
    • No? ¿Este estado necesita solo un componente hijo?
    • ¿Eh? Muévalo a este componente secundario (use la ubicación conjunta). Vamos a 3.
    • No? ¿Este estado necesita componentes primarios o vecinos (componentes "hermanos", es decir, componentes secundarios del mismo componente primario)?
      • ¿Eh? Mueva el estado anterior al componente principal. Ir a 3
      • No? Vamos a 4
  • 4 Dejar como está. Ir a 5
  • 5 ¿Hay algún problema con la perforación de puntales?
    • ¿Eh? Movemos este estado al proveedor de contexto y representamos este proveedor en el componente en el que se administra el estado. Ir a 6
    • No? Ir a 6
  • 6 Enviamos la solicitud. Cuando aparezcan nuevos requisitos, vaya a 1

Es importante que este proceso sea parte de su proceso regular de refactorización / mantenimiento de aplicaciones. Si su condición no se eleva donde debería elevarse, entonces este estado dejará de funcionar correctamente y lo notará. Sin embargo, si no sigue el método de ubicación conjunta de estados y no baja los estados en la jerarquía de componentes, entonces su aplicación continuará funcionando. Debido a esto, no notará de inmediato problemas de rendimiento y capacidad de administración que se acumularán gradualmente.


Conclusión


En general, los desarrolladores entienden muy bien cuándo es necesario elevar el estado ("estado de elevación") cuando es necesario, pero no entendemos tan bien cuándo es necesario reducir el estado. Le sugiero que eche un vistazo al código de su aplicación y piense en dónde podría omitirse el estado aplicando el principio de "ubicación conjunta". Pregúntese: "¿Necesito el estado isOpen de una ventana modal en el Editor?" (Lo más probable es que la respuesta sea no).


Use el principio de uso compartido y su código será más fácil y rápido.


Buena suerte

Source: https://habr.com/ru/post/485032/


All Articles