javascript - relleno - ¿Por qué los accesorios JSX no deberían usar funciones de flecha o enlazar?
map javascript (5)
Esto se debe a que una función de flecha aparentemente creará una nueva instancia de la función en cada render si se usa en una propiedad JSX. Esto podría crear una gran presión sobre el recolector de basura y también impedirá que el navegador optimice las "rutas activas", ya que las funciones se descartará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
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 (dentro de
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 debe evitarse? ¿Y cuál es la mejor manera de hacerlo?
Para evitar crear nuevas funciones con los mismos argumentos, puede memorizar el resultado del enlace de la función, aquí hay una sencilla utilidad llamada
memobind
para hacerlo:
https://github.com/supnate/memobind
Puede usar las funciones de flecha con la biblioteca de react-cached-handler , no tiene que preocuparse por el rendimiento de representación:
Nota: internamente almacena en caché sus funciones de flecha por la tecla especificada, ¡no tiene que 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:
- Manejadores nombrados
- Manejar eventos por funciones de flecha
- Acceso a la clave, argumentos personalizados y el evento original.
- Rendimiento de representación de componentes
- Contexto personalizado para manejadores
Usar funciones en línea como esta está perfectamente bien. La regla de linting está desactualizada.
Esta regla es de una época en que las funciones de flecha no eran tan comunes y la gente usaba .bind (esto), que solía ser lenta. El problema de rendimiento se ha solucionado en Chrome 49.
Tenga en cuenta que no pasa funciones en línea como accesorios a un componente secundario.
Ryan Florence, el autor de React Router, ha escrito una gran pieza sobre esto:
https://cdb.reacttraining.com/react-inline-functions-and-performance-bdff784f5578
¿Por qué no deberías usar funciones de flecha en línea en accesorios JSX?
El uso de funciones de flecha o enlace en JSX es una mala práctica que perjudica el rendimiento, porque la función se recrea en cada render.
-
Cada vez que se crea una función, la función anterior es recolección de basura. La reproducción de muchos elementos puede crear un jank en las animaciones.
-
El uso de una función de flecha en línea hará que
PureComponent
s, y los componentes que usanshallowCompare
en el métodoshouldComponentUpdate
seshouldComponentUpdate
a procesar de todos modos. Dado que la función de flecha prop se recrea cada vez, la comparación superficial lo identificará como un cambio a un accesorio, y el componente se volverá a mostrar.
Como puede ver en los siguientes 2 ejemplos: cuando usamos la función de flecha en línea, el componente
<Button>
se vuelve a procesar cada vez (la consola muestra el texto del "botón de renderizado").
Ejemplo 1 - PureComponent sin controlador en línea
class Button extends React.PureComponent {
render() {
const { onClick } = this.props;
console.log(''render button'');
return (
<button onClick={ onClick }>Click</button>
);
}
}
class Parent extends React.Component {
state = {
counter: 0
}
onClick = () => this.setState((prevState) => ({
counter: prevState.counter + 1
}));
render() {
const { counter } = this.state;
return (
<div>
<Button onClick={ this.onClick } />
<div>{ counter }</div>
</div>
);
}
}
ReactDOM.render(
<Parent />,
document.getElementById(''root'')
);
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Ejemplo 2 - PureComponent con controlador en línea
class Button extends React.PureComponent {
render() {
const { onClick } = this.props;
console.log(''render button'');
return (
<button onClick={ onClick }>Click</button>
);
}
}
class Parent extends React.Component {
state = {
counter: 0
}
render() {
const { counter } = this.state;
return (
<div>
<Button onClick={ () => this.setState((prevState) => ({
counter: prevState.counter + 1
})) } />
<div>{ counter }</div>
</div>
);
}
}
ReactDOM.render(
<Parent />,
document.getElementById(''root'')
);
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Métodos vinculantes para
this
sin incluir funciones de flecha
-
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> ); } }
-
Enlace de un método utilizando los proposal-class-fields con una función de flecha. Como se trata de una propuesta de la etapa 3, deberá agregar el preajuste de la etapa 3 o las propiedades de la clase se transformarán en 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 funciones 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 recreará cada vez que se represente el componente.
Si la función se pasa como accesorios (o mediante contexto) a un componente secundario (
Button
en este caso), ese elemento secundario también se volverá a representar.
Ejemplo 1 - Componente de función con una devolución de llamada interna:
const { memo, useState } = React;
const Button = memo(({ onClick }) => console.log(''render button'') || (
<button onClick={onClick}>Click</button>
));
const Parent = () => {
const [counter, setCounter] = useState(0);
const increment = () => setCounter(counter => counter + 1); // the function is recreated all the time
return (
<div>
<Button onClick={increment} />
<div>{counter}</div>
</div>
);
}
ReactDOM.render(
<Parent />,
document.getElementById(''root'')
);
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>
Para resolver este problema, podemos ajustar la devolución de llamada con el
useCallback()
y establecer las dependencias en una matriz vacía.
Nota:
la función generada
useState
acepta una función de actualización, que proporciona el estado actual.
De esta manera, no necesitamos 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:
const { memo, useState, useCallback } = React;
const Button = memo(({ onClick }) => console.log(''render button'') || (
<button onClick={onClick}>Click</button>
));
const Parent = () => {
const [counter, setCounter] = useState(0);
const increment = useCallback(() => setCounter(counter => counter + 1), []);
return (
<div>
<Button onClick={increment} />
<div>{counter}</div>
</div>
);
}
ReactDOM.render(
<Parent />,
document.getElementById(''root'')
);
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>