¿Por qué los accesorios JSX no deberían usar funciones de flecha o vincularse?

103

Estoy ejecutando lint con mi aplicación React y recibo este error:

error    JSX props should not use arrow functions        react/jsx-no-bind

Y aquí es donde estoy ejecutando la función de flecha (adentro onClick):

{this.state.photos.map(tile => (
  <span key={tile.img}>
    <Checkbox
      defaultChecked={tile.checked}
      onCheck={() => this.selectPicture(tile)}
      style={{position: 'absolute', zIndex: 99, padding: 5, backgroundColor: 'rgba(255, 255, 255, 0.72)'}}
    />
    <GridTile
      title={tile.title}
      subtitle={<span>by <b>{tile.author}</b></span>}
      actionIcon={<IconButton onClick={() => this.handleDelete(tile)}><Delete color="white"/></IconButton>}
    >
      <img onClick={() => this.handleOpen(tile.img)} src={tile.img} style={{cursor: 'pointer'}}/>
    </GridTile>
  </span>
))}

¿Es esta una mala práctica que debería evitarse? ¿Y cuál es la mejor forma de hacerlo?

KadoBOT
fuente

Respuestas:

170

Por que no debería usar funciones de flecha en línea en accesorios JSX

Usar funciones de flecha o enlaces en JSX es una mala práctica que perjudica el rendimiento, porque la función se recrea en cada render.

  1. Siempre que se crea una función, la función anterior se recolecta como basura. Volver a renderizar muchos elementos puede crear un jank en las animaciones.

  2. El uso de una función de flecha en línea hará que los PureComponents y los componentes que se usan shallowCompareen el shouldComponentUpdatemétodo se vuelvan a procesar de todos modos. Dado que el accesorio de función de flecha se recrea cada vez, la comparación superficial lo identificará como un cambio en un accesorio y el componente se volverá a procesar.

Como puede ver en los siguientes 2 ejemplos, cuando usamos la función de flecha en línea, el <Button>componente se vuelve a generar cada vez (la consola muestra el texto del 'botón de procesamiento').

Ejemplo 1: PureComponent sin controlador en línea

Ejemplo 2: PureComponent con controlador en línea

Enlazar métodos thissin funciones de flecha en línea

  1. Vinculando el método manualmente en el constructor:

    class Button extends React.Component {
      constructor(props, context) {
        super(props, context);
    
        this.cb = this.cb.bind(this);
      }
    
      cb() {
    
      }
    
      render() {
        return (
          <button onClick={ this.cb }>Click</button>
        );
      }
    }
  2. Vincular un método utilizando los campos de clase de propuesta con una función de flecha. Como se trata de una propuesta de etapa 3, deberá agregar el ajuste preestablecido de etapa 3 o la transformación de propiedades de clase a su configuración de babel.

    class Button extends React.Component {
      cb = () => { // the class property is initialized with an arrow function that binds this to the class
    
      }
    
      render() {
        return (
          <button onClick={ this.cb }>Click</button>
        );
      }
    }

Componentes de función con devoluciones de llamada internas

Cuando creamos una función interna (controlador de eventos, por ejemplo) dentro de un componente de función, la función se volverá a crear cada vez que se procese el componente. Si la función se pasa como accesorios (o mediante contexto) a un componente hijo ( Buttonen este caso), ese hijo también se volverá a renderizar.

Ejemplo 1 - Componente de función con una devolución de llamada interna:

Para resolver este problema, podemos envolver la devolución de llamada con el useCallback()gancho y establecer las dependencias en una matriz vacía.

Nota: la useStatefunción generada acepta una función de actualización, que proporciona el estado actual. De esta forma, no es necesario establecer el estado actual como una dependencia de useCallback.

Ejemplo 2 - Componente de función con una devolución de llamada interna envuelta con useCallback:

Ori Drori
fuente
3
¿Cómo logra esto en componentes sin estado?
lux
4
Los componentes sin estado (función) no tienen this, por lo que no hay nada que vincular. Por lo general, los métodos los proporciona un componente inteligente de envoltura.
Ori Drori
39
@OriDrori: ¿Cómo funciona eso cuando necesitas pasar datos en la devolución de llamada? onClick={() => { onTodoClick(todo.id) }
Adam-Beck
4
@ adam-beck: agréguelo dentro de la definición del método de devolución de llamada en la clase cb() { onTodoClick(this.props.todo.id); }.
Ori Drori
2
@ adam-beck Creo que esta es la forma de usar useCallbackcon valor dinámico. stackoverflow.com/questions/55006061/…
Shota Tamura
9

Esto se debe a que una función de flecha aparentemente creará una nueva instancia de la función en cada representación si se usa en una propiedad JSX. Esto puede crear una gran presión sobre el recolector de basura y también impedirá que el navegador optimice cualquier "ruta activa", ya que las funciones se desecharán en lugar de reutilizarse.

Puede ver la explicación completa y más información en https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md

Karl-Johan Sjögren
fuente
No solo eso. Crear las nuevas instancias de función cada vez significa que se modifica el estado y cuando se modifica el estado de un componente, se volverá a renderizar. Dado que una de las razones principales para usar React es solo renderizar elementos que cambian, usar bindlas funciones de flecha aquí es dispararse en el pie. Sin embargo, no está bien documentado, especialmente en el caso de trabajar con mapmatrices de ping dentro de Listas, etc.
hippietrail
"Crear las nuevas instancias de función cada vez significa que se modifica el estado" ¿qué quieres decir con eso? No hay ningún estado en la pregunta
uno de ellos
4

Para evitar crear nuevas funciones con los mismos argumentos, puede memorizar el resultado de vinculación de la función, aquí hay una utilidad simple nombrada memobindpara hacerlo: https://github.com/supnate/memobind

supNate
fuente
4

Usar funciones en línea como esta está perfectamente bien. La regla de pelado está desactualizada.

Esta regla es de una época en que las funciones de flecha no eran tan comunes y la gente usaba .bind (this), que solía ser lento. El problema de rendimiento se ha solucionado en Chrome 49.

Preste atención a no pasar funciones en línea como accesorios a un componente secundario.

Ryan Florence, el autor de React Router, ha escrito un gran artículo sobre esto:

https://cdb.reacttraining.com/react-inline-functions-and-performance-bdff784f5578

sbaechler
fuente
¿Puede mostrar cómo escribir una prueba unitaria en componentes con funciones de flecha en línea?
krankuba
1
@krankuba De esto no se trata esta pregunta. Aún puede pasar funciones anónimas que no están definidas en línea pero que aún no se pueden probar.
sbaechler
-1

Puede usar las funciones de flecha usando react-cached-handler biblioteca , no hay necesidad de preocuparse por el rendimiento de renderizado:

Nota: Internamente, almacena en caché las funciones de flecha con la tecla especificada, ¡no hay necesidad de preocuparse por volver a renderizar!

render() {

  return <div>
  {
        this.props.photos.map(photo=>
          <Photo key={photo.url}
            onClick={this.handler(photo.url, (url) => { 
                 console.log(url) })}
          />)
   }
 </div>

}

Otras características:

  • Controladores nombrados
  • Manejar eventos mediante funciones de flecha
  • Acceso a la clave, argumentos personalizados y el evento original
  • Rendimiento de representación de componentes
  • Contexto personalizado para controladores
Ghominejad
fuente
La pregunta era por qué no podemos usarlo. No cómo usarlo con algún otro truco.
kapil
-1

¿Por qué los accesorios JSX no deberían usar funciones de flecha o vincularse?

Principalmente, porque las funciones en línea pueden romper la memorización de componentes optimizados:

Tradicionalmente, las preocupaciones sobre el rendimiento en torno a las funciones en línea en React se han relacionado con cómo pasar nuevas devoluciones de llamada en cada render rompe las shouldComponentUpdateoptimizaciones en los componentes secundarios. ( docs )

Se trata menos del costo de creación de funciones adicionales:

Los problemas de rendimiento Function.prototype.bind se solucionaron aquí y las funciones de flecha son nativas o son transpiladas por babel a funciones simples; en ambos casos podemos asumir que no es lento. ( Reaccionar entrenamiento )

Creo que las personas que afirman que la creación de funciones es costosa siempre han estado mal informadas (el equipo de React nunca dijo esto). ( Tuit )

Cuando es el react/jsx-no-bind útil regla?

Desea asegurarse de que los componentes memorizados funcionen según lo previsto:

  • React.memo (para componentes de funciones)
  • PureComponento personalizado shouldComponentUpdate(para componentes de clase)

Al obedecer esta regla, se pasan referencias a objetos de funciones estables. Por lo tanto, los componentes anteriores pueden optimizar el rendimiento al evitar que se vuelvan a renderizar, cuando los accesorios anteriores no han cambiado.

¿Cómo solucionar el error de ESLint?

Clases: defina el controlador como método o propiedad de clase para el thisenlace.
Ganchos: usouseCallback .

Plano medio

En muchos casos, las funciones en línea son muy cómodas de usar y absolutamente buenas en términos de requisitos de rendimiento. Desafortunadamente, esta regla no puede limitarse solo a tipos de componentes memorizados. Si aún desea usarlo en todos los ámbitos, puede, por ejemplo, deshabilitarlo para nodos DOM simples:

rules: {
  "react/jsx-no-bind": [ "error", { ignoreDOMComponents: true } ],
}

const Comp = () => <span onClick={() => console.log("Hello!")} />; // no warning
ford04
fuente