我们应该避免在render内部绑定方法,因为在重新渲染期间它会创建新方法而不是使用旧方法,这会影响性能。
对于这样的场景:
<input onChange = { this._handleChange.bind(this) } ...../>
我们可以在构造函数中绑定_handleChange
方法:
this._handleChange = this._handleChange.bind(this);
或者我们可以使用property initializer syntax:
_handleChange = () => {....}
现在让我们考虑一下我们想要传递一些额外参数的情况,让我们说在一个简单的待办事项应用中,点击项目我需要从数组中删除项目,因为我需要传递项目索引或待办事项每个onClick方法中的名称:
todos.map(el => <div key={el} onClick={this._deleteTodo.bind(this, el)}> {el} </div>)
现在假设todo名称是唯一的。
根据DOC:
此语法的问题是创建了不同的回调 每次组件渲染时。
问题:
如何在render方法中避免这种绑定方式或者有什么替代方法?
请提供任何参考或示例,谢谢。
答案 0 :(得分:21)
首先:一个简单的解决方案是为map函数内的内容创建一个组件,并将值作为props传递,当您从子组件调用该函数时,您可以将值传递给该函数作为道具传递下来。
<强>父强>
deleteTodo = (val) => {
console.log(val)
}
todos.map(el =>
<MyComponent val={el} onClick={this.deleteTodo}/>
)
<强> MyComponent的强>
class MyComponent extends React.Component {
deleteTodo = () => {
this.props.onClick(this.props.val);
}
render() {
return <div onClick={this.deleteTodo}> {this.props.val} </div>
}
}
示例代码段
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>
编辑:
第二:另一种方法是使用memoize并返回一个函数
constructor() {
super();
this._deleteTodoListener = _.memoize(
this._deleteTodo, (element) => {
return element.hashCode();
}
)
}
_deleteTodo = (element) => {
//delete handling here
}
并使用它
todos.map(el => <div key={el} onClick={this._deleteTodoListener(el)}> {el} </div>)
P.S。然而,这不是最佳解决方案,仍将导致 正在创建多个功能,但仍然是一个改进 最初的案例。
第三:但是,对此更合适的解决方案是将attribute
添加到最顶层的div并从event
获取值
_deleteTodo = (e) => {
console.log(e.currentTarget.getAttribute('data-value'));
}
todos.map(el => <div key={el} data-value={el} onClick={this._deleteTodo}> {el} </div>)
但是,在这种情况下,使用toString方法将属性转换为字符串,因此对象将转换为[Object Object]
而像["1" , "2", "3"]
这样的数组将转换为"1, 2, 3"
答案 1 :(得分:4)
这个答案https://stackoverflow.com/a/45053753/2808062绝对是详尽无遗的,但我认为通过在子组件中实现适当的shouldComponentUpdate
来解决这个问题会更好。
即使道具完全相同,以下代码仍然会重新呈现孩子,除非他们在自己的shouldComponentUpdate
中阻止它们(他们可能会从PureComponent
继承它):
handleChildClick = itemId => {}
render() {
return this.props.array.map(itemData => <Child onClick={this.handleChildClick} data={itemData})
}
证明:https://jsfiddle.net/69z2wepo/92281/。
因此,为了避免重新渲染,子组件必须实现shouldComponentUpdate
。现在,唯一合理的实现是完全忽略onClick
,无论它是否已经改变:
shouldComponentUpdate(nextProps) {
return this.props.array !== nextProps.array;
}
答案 2 :(得分:1)
如何在render方法中避免这种绑定方式或者是什么 替代品呢?
如果您关心重新渲染,那么shouldComponentUpdate
和PureComponent
就是您的朋友,他们会帮助您优化渲染。
您必须从“Parent”中提取“Child”组件,并始终传递相同的道具并实施shouldComponentUpdate
或使用PureComponent
。我们想要的是一个移除孩子的情况,不应该重新渲染其他孩子。
示例强>
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'));
演示:https://codesandbox.io/s/99nZGlyZ
预期行为
<App /> render()
<Product id=1... render()
<Product id=2... render()
当我们删除<Product id=2 ...
时,只会重新呈现<App />
。
要在演示中查看这些消息,请打开开发工具控制台。
FrançoisZaninotto在文章中使用和描述了相同的技术:React is Slow, React is Fast: Optimizing React Apps in Practice。
答案 3 :(得分:0)
Documentation鼓励使用数据属性并从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>
)
note也只有在您遇到性能问题时才有意义:
可以在渲染方法中使用箭头功能吗?
一般来说,是的,可以,而且通常是最简单的方法 将参数传递给回调函数。
如果确实存在性能问题,请进行优化!