Cómo evitar funciones de enlace o flecha en línea dentro del método de renderizado
Deberíamos evitar la vinculación de métodos dentro del renderizado porque durante el nuevo renderizado se crearán nuevos métodos en lugar de usar el antiguo, lo que afectará el rendimiento.
Entonces, para escenarios como este:
<input onChange = { this._handleChange.bind(this) } ...../>
Podemos vincular _handleChange
el método ya sea en el constructor:
this._handleChange = this._handleChange.bind(this);
O podemos usar la sintaxis del inicializador de propiedad :
_handleChange = () => {....}
Ahora consideremos el caso en el que queremos pasar algún parámetro adicional, digamos en una aplicación de tareas simple, al hacer clic en el elemento necesito eliminar el elemento de la matriz, para eso necesito pasar el índice del elemento o el nombre de la tarea en cada uno. Método al hacer clic:
todos.map(el => <div key={el} onClick={this._deleteTodo.bind(this, el)}> {el} </div>)
Por ahora simplemente asuma que los nombres de las tareas pendientes son únicos.
Según DOC :
El problema con esta sintaxis es que se crea una devolución de llamada diferente cada vez que se procesa el componente.
Pregunta:
¿Cómo evitar esta forma de vincularse dentro del método de renderizado o cuáles son las alternativas a esto?
Por favor proporcione cualquier referencia o ejemplo, gracias.
Primero: una solución simple será crear un componente para el contenido dentro de una función de mapa y pasar los valores como accesorios y cuando llame a la función desde el componente secundario, podrá pasar el valor a la función transmitida como accesorios.
Padre
deleteTodo = (val) => {
console.log(val)
}
todos.map(el =>
<MyComponent val={el} onClick={this.deleteTodo}/>
)
Mi componente
class MyComponent extends React.Component {
deleteTodo = () => {
this.props.onClick(this.props.val);
}
render() {
return <div onClick={this.deleteTodo}> {this.props.val} </div>
}
}
Fragmento de muestra
class Parent extends React.Component {
_deleteTodo = (val) => {
console.log(val)
}
render() {
var todos = ['a', 'b', 'c'];
return (
<div>{todos.map(el =>
<MyComponent key={el} val={el} onClick={this._deleteTodo}/>
)}</div>
)
}
}
class MyComponent extends React.Component {
_deleteTodo = () => {
console.log('here'); this.props.onClick(this.props.val);
}
render() {
return <div onClick={this._deleteTodo}> {this.props.val} </div>
}
}
ReactDOM.render(<Parent/>, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
EDITAR:
Segundo: el otro enfoque sería usar memorizar y devolver una función.
constructor() {
super();
this._deleteTodoListener = _.memoize(
this._deleteTodo, (element) => {
return element.hashCode();
}
)
}
_deleteTodo = (element) => {
//delete handling here
}
y usarlo como
todos.map(el => <div key={el} onClick={this._deleteTodoListener(el)}> {el} </div>)
PD: Sin embargo, esta no es la mejor solución y aún así resultará en la creación de múltiples funciones, pero sigue siendo una mejora con respecto al caso inicial.
Tercero: sin embargo, una solución más apropiada para esto será agregar un attribute
al div superior y obtener el valor de event
like
_deleteTodo = (e) => {
console.log(e.currentTarget.getAttribute('data-value'));
}
todos.map(el => <div key={el} data-value={el} onClick={this._deleteTodo}> {el} </div>)
Sin embargo, en este caso los atributos se convierten a cadena usando el método toString y, por lo tanto, el objeto se convertirá en [Object Object]
una matriz ["1" , "2", "3"]
como"1, 2, 3"
¿Cómo evitar esta forma de vincularse dentro del método de renderizado o cuáles son las alternativas a esto?
Si te interesa volver a renderizar, entonces shouldComponentUpdate
eres PureComponent
tus amigos y ellos te ayudarán a optimizar el renderizado.
Tienes que extraer el componente "Secundario" del "Padre" y pasar siempre los mismos accesorios e implementar shouldComponentUpdate
o usar PureComponent
. Lo que queremos es un caso en el que eliminemos un niño, otros niños no deberían volver a representarse.
Ejemplo
import React, { Component, PureComponent } from 'react';
import { render } from 'react-dom';
class Product extends PureComponent {
render() {
const { id, name, onDelete } = this.props;
console.log(`<Product id=${id} /> render()`);
return (
<li>
{id} - {name}
<button onClick={() => onDelete(id)}>Delete</button>
</li>
);
}
}
class App extends Component {
constructor(props) {
super(props);
this.state = {
products: [
{ id: 1, name: 'Foo' },
{ id: 2, name: 'Bar' },
],
};
this.handleDelete = this.handleDelete.bind(this);
}
handleDelete(productId) {
this.setState(prevState => ({
products: prevState.products.filter(product => product.id !== productId),
}));
}
render() {
console.log(`<App /> render()`);
return (
<div>
<h1>Products</h1>
<ul>
{
this.state.products.map(product => (
<Product
key={product.id}
onDelete={this.handleDelete}
{...product}
/>
))
}
</ul>
</div>
);
}
}
render(<App />, document.getElementById('root'));
Demostración: https://codesandbox.io/s/99nZGlyZ
Comportamiento esperado
<App /> render()
<Product id=1... render()
<Product id=2... render()
Cuando eliminamos <Product id=2 ...
solo <App />
se vuelve a renderizar.
- prestar()
Para ver esos mensajes en la demostración, abra la consola de herramientas de desarrollo.
La misma técnica se utiliza y se describe en el artículo: React es lento, React es rápido: optimización de las aplicaciones React en la práctica de François Zaninotto.
La documentación recomienda utilizar atributos de datos y acceder a ellos desde dentro evt.target.dataset
:
_deleteTodo = (evt) => {
const elementToDelete = evt.target.dataset.el;
this.setState(prevState => ({
todos: prevState.todos.filter(el => el !== elementToDelete)
}))
}
// and from render:
todos.map(
el => <div key={el} data-el={el} onClick={this._deleteTodo}> {el} </div>
)
También tenga en cuenta que esto tiene sentido sólo cuando tiene problemas de rendimiento:
¿Está bien utilizar funciones de flecha en los métodos de renderizado?
En términos generales, sí, está bien y, a menudo, es la forma más sencilla de pasar parámetros a funciones de devolución de llamada.
Si tiene problemas de rendimiento, ¡optimice!