保证渲染的性能

为了保障组件的性能, 我们有的时候会从组件渲染的角度出发.

更干净的render函数? 这个概念可能会有点让人疑惑.

其实在这里干净是指我们在shouldComponentUpdate这个生命周期函数里面去做浅比较, 从而避免不必要的渲染.

关于上面的干净渲染, 现有的一些实现包括React.PureComponent, PureRenderMixin, recompose/pure 等等.

第一个例子

坏实践
  1. class Table extends PureComponent {
  2. render() {
  3. return (
  4. <div>
  5. {this.props.items.map(i =>
  6. <Cell data={i} options={this.props.options || []}/>
  7. )}
  8. </div>
  9. );
  10. }
  11. }

这种写法的问题在于{this.props.options || []}- 这种写法会导致所有的Cell都被重新渲染即使只有一个cell发生了改变. 为什么会发生这种事呢?

仔细观察你会发现, options这个数组被传到了Cell这个组件上, 一般情况下, 这不会导致什么问题. 因为如果有其他的Cell组件, 组件会在有props发生改变的时候浅对比props并且跳过渲染(因为对于其他Cell组件, props并没有发生改变). 但是在这个例子里面, 当options为null时, 一个默认的空数组就会被当成Props传到组件里面去. 事实上每次传入的[]都相当于创建了新的Array实例. 在JavaScript里面, 不同的实例是有不同的实体的, 所以浅比较在这种情况下总是会返回false, 然后组件就会被重新渲染. 因为两个实体不是同一个实体. 这就完全破坏了React对于我们组件渲染的优化.

好实践
  1. const defaultval = []; // <--- 也可以使用defaultProps
  2. class Table extends PureComponent {
  3. render() {
  4. return (
  5. <div>
  6. {this.props.items.map(i =>
  7. <Cell data={i} options={this.props.options || defaultval}/>
  8. )}
  9. </div>
  10. );
  11. }
  12. }

第二个例子

在render函数里面调用函数也可能导致和上面相同的问题.

坏实践
  1. class App extends PureComponent {
  2. render() {
  3. return <MyInput
  4. onChange={e => this.props.update(e.target.value)}/>;
  5. }
  6. }
又一个坏实践
  1. class App extends PureComponent {
  2. update(e) {
  3. this.props.update(e.target.value);
  4. }
  5. render() {
  6. return <MyInput onChange={this.update.bind(this)}/>;
  7. }
  8. }

在上面的两个坏实践中, 每次我们都会去创建一个新的函数实体. 和第一个例子类似, 新的函数实体会让我们的浅比较返回false, 导致组件被重新渲染.
所以我们需要在更早的时候去bind我们的函数.

好实践
  1. class App extends PureComponent {
  2. constructor(props) {
  3. super(props);
  4. this.update = this.update.bind(this);
  5. }
  6. update(e) {
  7. this.props.update(e.target.value);
  8. }
  9. render() {
  10. return <MyInput onChange={this.update}/>;
  11. }
  12. }
坏实践
  1. class Component extends React.Component {
  2. state = {clicked: false};
  3. onClick() {
  4. this.setState({clicked: true})
  5. }
  6. render() {
  7. // 如果options为空的话, 每次都会创建一个新的{test:1}对象
  8. const options = this.props.options || {test: 1};
  9. return <Something
  10. options={options}
  11. // New function created each render
  12. onClick={this.onClick.bind(this)}
  13. // New function & closure created each render
  14. onTouchTap={(event) => this.onClick(event)
  15. />
  16. }
  17. }
好实践
  1. class Component extends React.Component {
  2. state = {clicked: false};
  3. options = {test: 1};
  4. onClick = () => {
  5. this.setState({clicked: true})
  6. };
  7. render() {
  8. // Options这个对象只被创建了一次.
  9. const options = this.props.options || this.options;
  10. return <Something
  11. options={options}
  12. onClick={this.onClick} // 函数只创建一次, 只绑定一次
  13. onTouchTap={this.onClick} // 函数只创建一次, 只绑定一次
  14. />
  15. }
  16. }

参考资料: