Bienvenido, les saluda Miguel y para hoy les traigo un nuevo artículo.
Esta publicación de blog explora el costo de rendimiento de las funciones en línea en una aplicación React. Antes de comenzar, intentemos comprender qué significa la función en línea en el contexto de una aplicación React.
Índice
¿Qué es una función en línea?
En pocas palabras, una función en línea es una función que se define y se transmite dentro del método de renderizado de un componente de React.
Entendamos esto con un ejemplo básico de cómo se vería una función en línea en una aplicación React:
La propiedad onClick, en el ejemplo anterior, se pasa como una función en línea que llama a this.setState
. La función se define dentro del método de representación, a menudo en línea con JSX.
En el contexto de las aplicaciones React, este es un patrón muy popular y ampliamente utilizado.
Comencemos enumerando algunos patrones y técnicas comunes donde se usan funciones en línea en una aplicación React:
Una propiedad de componente que espera una función como valor. Esta función debe devolver un elemento JSX, de ahí el nombre. Render prop
es un buen candidato para funciones en línea.
render() { return ( <ListView items={items} render={({ item }) => (<div>{item.label}</div>)} /> ); }
Los controladores de eventos DOM a menudo hacen una llamada a setState
o invocan algún efecto en la aplicación React, como enviar datos a un servidor API.
<button onClick={() => { this.setState({ count: this.state.count + 1 }); }}> COUNT ({this.state.count}) </button>
A menudo, un componente hijo requiere que un controlador de eventos personalizado se transmita como accesorios. La función en línea se usa generalmente en este escenario.
<Button onTap={() => { this.nextPage(); }}>Next<Button>
Alternativas a la función en línea
Uno de los patrones más comunes es definir la función dentro del componente de la clase y luego vincular el contexto a la función en el constructor.
Solo necesitamos vincular el contexto actual si queremos usar esta palabra clave dentro de la función del controlador.
export default class CounterApp extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; this.increaseCount = this.increaseCount.bind(this); } increaseCount() { this.setState({ count: this.state.count + 1 }); } render() { return ( <div className="App"> <button onClick={this.increaseCount}>COUNT ({this.state.count})</button> </div> ); } }
Otro patrón común es vincular el contexto en línea cuando la función se transmite. Eventualmente, esto se vuelve repetitivo y, por lo tanto, el primer enfoque es más popular.
render() { return ( <div className="App"> <button onClick={this.increaseCount.bind(this)}>COUNT ({this.state.count})</button> </div> ); }
increaseCount = () => { this.setState({ count: this.state.count + 1 }); }; render() { return ( <div className="App"> <button onClick={this.increaseCount}> COUNT ({this.state.count}) </button> </div> ); }
Hay varios otros enfoques que la comunidad de desarrollo de React ha ideado, como usar un método auxiliar para vincular todas las funciones automáticamente en el constructor.
Después de comprender las funciones en línea con sus ejemplos y también de echar un vistazo a algunas alternativas, veamos por qué las funciones en línea son tan populares y ampliamente utilizadas.
Por qué usar la función en línea
Las definiciones de funciones en línea están justo donde se invocan o transmiten. Esto significa que las funciones en línea son más fáciles de escribir, especialmente cuando el cuerpo de la función es de unas pocas instrucciones, como llamar a setState
. Esto también funciona bien en bucles.
Por la misma razón, las funciones en línea también hacen que el código sea más organizado y legible.
Las funciones de flecha en línea conservan el contexto, lo que significa que los desarrolladores pueden usarlo sin tener que preocuparse por el contexto de ejecución actual o vincular explícitamente un contexto a la función.
<Button onTap={() => { this.prevPage(); }}>Previous<Button>
Las funciones en línea hacen que el valor del alcance principal esté disponible dentro de la definición de la función.
Da como resultado un código más intuitivo y los desarrolladores deben transmitir menos parámetros. Entendamos esto con un ejemplo.
render() { const { count } = this.state; return ( <div className="App"> <button onClick={() => { this.setState({ count: count + 1 }); }}> COUNT ({count}) </button> </div> ); }
Aquí, el valor del recuento está disponible para los controladores de eventos onClick
. Este comportamiento se llama closing over
Por estas razones, los desarrolladores de React utilizan mucho las funciones en línea. Dicho esto, la función en línea también ha sido un tema candente de debate debido a problemas de rendimiento.
Echemos un vistazo a algunos de estos argumentos.
Argumentos en contra de las funciones en línea
- Se define una nueva función cada vez que se llama al método render. Da lugar a una recogida de basura frecuente y, por tanto, a una pérdida de rendimiento.
- Hay una configuración de
eslint
que desaconseja el uso de la función en líneajsx-no-bind
. La idea detrás de esta regla es que cuando una función en línea se transmite a un componente hijo, React usa verificaciones de referencia para volver a renderizar el componente. Esto puede resultar en la representación del componente hijo una y otra vez como una referencia al valor de prop pasado, es decir, la función en línea. En este caso, no coincide con el original.
Supongamos que el componente ListItem
implementa el método shouldComponentUpdate
donde verifica la referencia de la propiedad onClick
.
Dado que las funciones en línea se crean cada vez que un componente vuelve a renderizarse, esto significa que el componente ListItem
hará referencia a una nueva función cada vez, lo que apunta a una ubicación diferente en la memoria.
La comparación comprueba shouldComponentUpdate
y le dice a React que vuelva a renderizar ListItem
aunque el comportamiento de la función en línea no cambie.
Esto da como resultado actualizaciones de DOM
innecesarias y eventualmente reduce el rendimiento de las aplicaciones.
Function.prototype.bind:
cuando no se usan funciones de flecha, la función en línea que se transmite debe estar vinculada a un contexto si se usa esta palabra clave dentro de la función.
La práctica de llamar a .bind
antes de pasar una función en línea plantea problemas de rendimiento, pero se ha solucionado.
Para navegadores más antiguos, Function.prototype.bind
se puede complementar con un polyfill
para el rendimiento.
Ahora que hemos resumido algunos argumentos a favor de las funciones en línea y algunos argumentos en contra, investiguemos y veamos cómo funcionan realmente las funciones en línea.
render() { return ( <div> {this.state.timeThen > this.state.timeNow ? ( <> <button onClick={() => { /* some action */ }} /> <button onClick={() => { /* another action */ }} /> </> ) : ( <button onClick={() => { /* yet another action */ }} /> )} </div> ); }
La optimización previa a menudo puede generar un código incorrecto. Por ejemplo, intentemos deshacernos de todas las definiciones de funciones en línea en el componente anterior y moverlas al constructor debido a problemas de rendimiento.
Luego tendríamos que definir 3
controladores de eventos personalizados en la definición de clase y vincular el contexto a las tres funciones en el constructor.
export default class CounterApp extends React.Component { constructor(props) { super(props); this.state = { timeThen: ..., timeNow: Date.now() }; this.someAction = this.someAction.bind(this); this.anotherAction = this.anotherAction.bind(this); this.yetAnotherAction = this.yetAnotherAction.bind(this); } someAction() { /* some action */ } anotherAction() { /* another action */ } yetAnotherAction() { /* yet another action */ } render() { return (<div> {this.state.timeThen > this.state.timeNow ? ( <> <button onClick={this.someAction} /> <button onClick={this.anotherAction} /> </> ) : ( <button onClick={this.yetAnotherAction} /> )}</div>); } }
Esto aumentaría significativamente el tiempo de inicialización del componente en comparación con las declaraciones de funciones en línea donde solo se definen y usan una o dos funciones a la vez en función del resultado de la condición timeThen> timeNow
.
Preocupaciones sobre los accesorios de renderizado
A render_prop
es un método que devuelve un elemento React y se usa para compartir el estado entre los componentes React.
Los accesorios de renderizado deben invocarse en cada renderizado, ya que comparten el estado entre los componentes principales y los elementos de React adjuntos.
Las funciones en línea son un buen candidato para su uso en render prop y no causarán ningún problema de rendimiento.
render() { return ( <ListView items={items} render={({ item }) => (<div>{item.label}</div>)} /> ) }
Aquí, la propiedad de renderización del componente ListView
devuelve una etiqueta encerrada en un div
. Dado que el componente adjunto nunca puede saber cuál es el valor de la variable del elemento, nunca puede ser un componente puro o tener una implementación significativa de shouldComponentUpdate ()
.
Esto elimina las preocupaciones sobre el uso de la función en línea en la propuesta de renderizado. De hecho, lo promueve en la mayoría de los casos.
En mi experiencia, los accesorios de renderizado en línea a veces pueden ser más difíciles de mantener, especialmente cuando el objeto de renderizado devuelve un componente más grande y complicado en términos de tamaño de código.
En tales casos, desglosar aún más el componente o tener un método separado que se transmita como prop de renderizado me ha funcionado bien.
PureComponents
y shouldComponentUpdate ():
Los componentes puros y varias implementaciones de shouldComponentUpdate
hacen una comparación estricta de tipos de accesorios y estado.
Estos actúan como potenciadores del rendimiento al permitir que React sepa cuándo o cuándo no activar un renderizado en función de los cambios en el estado y los accesorios.
Dado que las funciones en línea se crean en cada render, cuando dicho método se pasa a un componente puro o un componente que implementa el método shouldComponentUpdate
, puede conducir a un renderizado innecesario.
Esto se debe a la referencia modificada de la función en línea.
Para superar esto, considere omitir las comprobaciones en todas las funciones de apoyo en shouldComponentUpdate ()
. Esto supone que las funciones en línea que se pasan al componente solo son diferentes en referencia y no en comportamiento.
Si hay una diferencia en el comportamiento de la función transmitida, resultará en un render perdido y eventualmente dará lugar a errores en el estado y efectos del componente.
Una regla general es medir el rendimiento de la aplicación y optimizarla solo si es necesario.
El impacto en el rendimiento de la función en línea, que a menudo se clasifica en microoptimizaciones, es siempre un compromiso entre la legibilidad del código, la mejora del rendimiento, la organización del código, etc., que debe pensarse cuidadosamente caso por caso y debe evitarse la optimización previa.
En esta publicación de blog, observamos que las funciones en línea no necesariamente generan un gran costo de rendimiento.
Se utilizan ampliamente debido a la facilidad de escritura, lectura y organización de funciones en línea, especialmente cuando las definiciones de funciones en línea son breves y simples.
Gracias por leer este artículo.
Añadir comentario