react-optimized

react 性能优化

使用 shouldComponentUpdate

react 生命周期
upload successful

react 的生命周期可以看出,当 componentpropsstate 改变时,会调用 shouldComponentUpdate 确认要不要刷新。默认 shouldComponentUpdate 都是返回 true ,这样 component 就会 re-render 。我们可以重写 shouldComponentUpdate , 使之只在特定情况返回 true ,其他情况返回 false ,来避免不必要的 re-render

例子

比如组件的改变只依赖 props.colorstate.count ,可以像下面这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class CounterButton extends React.Component {
constructor(props) {
super(props);
this.state = {count: 1};
}

shouldComponentUpdate(nextProps, nextState) {
if (this.props.color !== nextProps.color) {
return true;
}
if (this.state.count !== nextState.count) {
return true;
}
return false;
}

render() {
return (
<button
color={this.props.color}
onClick={() => this.setState(state => ({count: state.count + 1}))}>
Count: {this.state.count}
</button>
);
}
}

上面例子中,我们在 shouldComponentUpdate 中判断 props.colorstate.count 前后有没变化,有就返回 true , 没有就返回 false ,避免了不必要的重新渲染。
可是当依赖的属性很多时,手写 shouldComponentUpdate 就会变得很麻烦。幸运的是 react 给我们提供了偷懒的工具—— React.PureComponent 。当组件继承自 React.PureComponent 时,内部会自动帮我们检查 propsstate 的所有字段前后有没发生变化,并决定要不要重新渲染,所以大部分情况不用再重写 shouldComponentUpdate
现在代码变成下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class CounterButton extends React.PureComponent {
constructor(props) {
super(props);
this.state = {count: 1};
}

render() {
return (
<button
color={this.props.color}
onClick={() => this.setState(state => ({count: state.count + 1}))}>
Count: {this.state.count}
</button>
);
}
}

属性内部保持不变性

使用 React.PureComponent 要注意的一点是在比较对象(object、array)时只比较引用,不对内部进行对比。看下面例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class ListOfWords extends React.PureComponent {
render() {
return <div>{this.props.words.join(',')}</div>;
}
}

class WordAdder extends React.Component {
constructor(props) {
super(props);
this.state = {
words: ['marklar']
};
this.handleClick = this.handleClick.bind(this);
}

handleClick() {
// words 的内部发生变化,引用并没有变化,所以 setState 后 ListWords 不会刷新
const words = this.state.words;
words.push('marklar');
this.setState({words: words});
}

render() {
return (
<div>
<button onClick={this.handleClick} />
<ListOfWords words={this.state.words} />
</div>
);
}
}

handleClick 使用 push , this.state.words 的内部发生变化,引用并没有变化,所以 setState 后 ListWords 不会刷新。解决办法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 办法1
handleClick() {
this.setState(state => ({
words: state.words.concat(['marklar'])
}));
}

// 办法2
handleClick() {
this.setState(state => ({
words: [...state.words, 'marklar'],
}));
};

上面代码都会生成一个新的 words 数组对象,这样 ListOfWords 就能比较出 props.words 前后发生变化,进行重新渲染。

保持不可变(Immutability)有以下好处:

  1. 我们可以对每个时间点的 propsstate 进行快照保存,这样就可以返回任意时间点的状态。在某些场景这非常有用,比如游戏中存档,文档编辑的 undo 和 redo。
  2. 让检测对象变化变得简单。对于可变对象的变化检测非常麻烦,要检测对象里的所有字段。而不可变对象的检测就很简单,只要检测前后引用是否发生变化。这点对于创建 React.PureComponent 是非常有用的。

Function Components

如果一个组件只有 render 方法,且没有 state ,那么可以使用 Function Components 。直接使用一个函数,入参为 props ,返回要渲染的内容。比如:

1
2
3
4
5
6
7
function Square(props) {
return (
<button className="square" onClick={props.onClick}>
{props.value}
</button>
);
}

总结

react 的性能优化可以从四方面入手:

  1. 使用 shouldComponentUpdate 避免不必要重新渲染
  2. 如果对于相同的 propsstate, render 返回的结果一样,那么就继承 React.PureComponent ,避免重新渲染。
  3. 保持对象的不可变性(Immutability),减少对象比较的消耗。
  4. 对于只有 render 方法,且没有 state 的组件使用 Function Components ,减少一层封装。