React事件处理程序箭头函数造成的重复渲染问题

Alan

Alan

Maintainer of blog

本文是以下文章的总结

问题引入#

components/User.jsx
import React from "react";
class User extends React.PureComponent {
render() {
const { name, onDeleteClick, id } = this.props;
console.log(`${name} just rendered`);
return (
<li>
<input type="button" value="Delete" onClick={onDeleteClick} />
{name}
</li>
);
}
}
export default User;
App.jsx
import React from "react";
import User from "./components/User";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
users: [
{ id: 1, name: "Cory" },
{ id: 2, name: "Meg" },
{ id: 3, name: "Bob" }
]
};
}
deleteUser = id => {
this.setState(prevState => {
return {
users: prevState.users.filter(user => user.id !== id)
};
});
};
render() {
return (
<div>
<h1>Users</h1>
<ul>
{this.state.users.map(user => {
return (
<User
key={user.id}
name={user.name}
id={user.id}
onDeleteClick={() => this.deleteUser(user.id)}
/>
);
})}
</ul>
</div>
);
}
}
export default App;

state或者props发生变化时, React会执行组件的render方法.

用户点击删除按钮之后触发以下流程:

  1. 触发 onDeleteClick 事件
  2. 执行 () => this.deleteUser(id)
  3. setState方法修改users状态
  4. 状态发生变化, 触发 <App >组件的render方法
  5. <App />组件的render方法会传递新的一个箭头函数给<User />组件的onDeleteClick属性
  6. 导致<User />的props发生变化, 重新执行<User />组件的render方法

原因就是箭头函数每次都是新的, 导致<App />组件每次执行render发放时, <User />props都会发生变化.

解决方案#

文章开始引用的博文里给的解决方案是<User />组件内部调用 onDeleteClick方法时把id传过去:

components/User.jsx
<input value="Delete" onClick={() => this.onDeleteClick(this.props.id)} />
App.jsx
<User key={user.id} name={user.name} id={user.id} onDeleteClick={this.deleteUser} />

但是这个方案有个缺陷, 就是子组件需要关心入参, 而且那个入参可能是子组件不知道的.

为了解决这个问题, 可以给state上的每个user数据添加一个delete函数:

App.jsx
class App extends React.Component {
constructor() {
// ...
this.state.users.forEach(item => {
item.onDelete = () => {
this.deleteUser(item.id);
}
});
// ...
}
render() {
// ...
<User
key={user.id}
name={user.name}
id={user.id}
onDeleteClick={user.onDelete}
/>
// ...
}
}