为了保障组件的性能, 我们有的时候会从组件渲染的角度出发.
更干净的render函数? 这个概念可能会有点让人疑惑.
其实在这里干净是指我们在shouldComponentUpdate
这个生命周期函数里面去做浅比较, 从而避免不必要的渲染.
关于上面的干净渲染, 现有的一些实现包括React.PureComponent, PureRenderMixin, recompose/pure 等等.
class Table extends PureComponent {
render() {
return (
<div>
{this.props.items.map(i =>
<Cell data={i} options={this.props.options || []}/>
)}
</div>
);
}
}
这种写法的问题在于{this.props.options || []}
- 这种写法会导致所有的Cell都被重新渲染即使只有一个cell发生了改变. 为什么会发生这种事呢?
仔细观察你会发现, options这个数组被传到了Cell这个组件上, 一般情况下, 这不会导致什么问题. 因为如果有其他的Cell组件, 组件会在有props发生改变的时候浅对比props并且跳过渲染(因为对于其他Cell组件, props并没有发生改变). 但是在这个例子里面, 当options为null时, 一个默认的空数组就会被当成Props传到组件里面去. 事实上每次传入的[]
都相当于创建了新的Array实例. 在JavaScript里面, 不同的实例是有不同的实体的, 所以浅比较在这种情况下总是会返回false, 然后组件就会被重新渲染. 因为两个实体不是同一个实体. 这就完全破坏了React对于我们组件渲染的优化.
const defaultval = []; // <--- 也可以使用defaultProps
class Table extends PureComponent {
render() {
return (
<div>
{this.props.items.map(i =>
<Cell data={i} options={this.props.options || defaultval}/>
)}
</div>
);
}
}
在render函数里面调用函数也可能导致和上面相同的问题.
class App extends PureComponent {
render() {
return <MyInput
onChange={e => this.props.update(e.target.value)}/>;
}
}
class App extends PureComponent {
update(e) {
this.props.update(e.target.value);
}
render() {
return <MyInput onChange={this.update.bind(this)}/>;
}
}
在上面的两个坏实践中, 每次我们都会去创建一个新的函数实体. 和第一个例子类似, 新的函数实体会让我们的浅比较返回false, 导致组件被重新渲染. 所以我们需要在更早的时候去bind我们的函数.
class App extends PureComponent {
constructor(props) {
super(props);
this.update = this.update.bind(this);
}
update(e) {
this.props.update(e.target.value);
}
render() {
return <MyInput onChange={this.update}/>;
}
}
class Component extends React.Component {
state = {clicked: false};
onClick() {
this.setState({clicked: true})
}
render() {
// 如果options为空的话, 每次都会创建一个新的{test:1}对象
const options = this.props.options || {test: 1};
return <Something
options={options}
// New function created each render
onClick={this.onClick.bind(this)}
// New function & closure created each render
onTouchTap={(event) => this.onClick(event)
/>
}
}
class Component extends React.Component {
state = {clicked: false};
options = {test: 1};
onClick = () => {
this.setState({clicked: true})
};
render() {
// Options这个对象只被创建了一次.
const options = this.props.options || this.options;
return <Something
options={options}
onClick={this.onClick} // 函数只创建一次, 只绑定一次
onTouchTap={this.onClick} // 函数只创建一次, 只绑定一次
/>
}
}