当props和state改变时,PureComponent会对props和state进行浅比较,Component并不会比较当前(current)和下一个状态(next)下的props和state,除非具有浅比较功能的shouldComponentUpdate方法被调用,此时Component组件是否渲染会基于方法的return值;
结合react源码,一个类组件(ClassComponent)是否更新,只有两个地方进行对比:1.有没有shouldComponentUpdate方法;2.PureComponent的浅比较判断。
下面我们结合例子(可直接复制到本地打开)对比:
import React, { Component, PureComponent } from 'react'; class ComponentDiff extends Component { render() { return ( <div> different between PureComponent and Component <br /> please open console <div style={{ display: 'flex' }}> <div style={{ marginRight: 200 }}> <ComponentDiffPure /> </div> <ComponentDiffNormal /> </div> </div> ); } } export default ComponentDiff; class ComponentDiffPure extends PureComponent { constructor() { super(); this.state = { text: 'true' }; console.log('pure-constructor'); } changeState = () => { this.setState({ text: 'false' }); }; render() { console.log('pure-render'); return ( <div> pure <button onClick={this.changeState}>Click</button> <div>{this.state.text}</div> </div> ); } } class ComponentDiffNormal extends Component { constructor() { super(); this.state = { text: 'true' }; console.log('normal-constructor'); } changeState = () => { this.setState({ text: 'false' }); }; render() { console.log('normal-render'); return ( <div> normal <button onClick={this.changeState}>Click</button> <div>{this.state.text}</div> </div> ); } }结合以上代码在控制台的输出,不难看出,在挂载时都会执行constructor和render方法,而在state改变时,PureComponent只在state的text值改变时执行了一次render方法,而Component会每次执行,这就是其浅对比功能体现的地方;
在探寻这个原理前我们需要知道什么是浅对比;
在对比当前和下一个props和state时,浅对比将对比值是否相同(如:‘aaa’ === ‘aaa’ or true === true),对于对象和数组,只去对比其引用是否相同; 这里就有一个使用PureComponent的问题,如果你对于传入的对象和数组进行更新时不能直接赋值,因为直接赋值并不改变其引用,也就不会引起重新渲染。对对象正确的处理方式可以通过ES6的assign方法或数组的扩展运算符或者第三方库,防止反悔一个新的对象;数组同理,需要返回一个新的数组才可以引起PureComponent的更新。
介于比较值和对象引用是低耗时的操作,所以如果对于多个子对象中的一个子对象进行更新时,浅对比的方式要比重新渲染所有子节点要快的多。
如果需要渲染一个列表,列表中每一个子组件都要传递一个唯一的参数到父方法。此时我们只需要将父组件的原型方法的引用传递给子组件,子组件中相应的属性将总是有同一个引用,自然不会造成不必要的重新渲染,例如:
<ListItem handleSelect={this.handleSelect} id={item.id} />在其子组件中创建一个引入当前传入的属性的类方法:
class ListItem extends Component { ... handleItemSelect(){ this.props.handleSelect(this.props.id) } ... }如果在循环过程中,每次循环都传递唯一的参数到方法中会导致每次父组件被调用时就会有一个新的函数被创建并传入自组建。改变每一个子组件的props就会造成他们所有子组件重新渲染,虽然其数据本身并没有发生变化,依然会被重新渲染。如下代码:
<ListItem handleSelect={()=>this.handleSelect(item.id)} />每次在组件渲染时都会有一个新的引用产生,props没有改变依然会有相同的派生数据生成,必然会造成不必要的重复渲染。
可以结合生命周期将派生数据缓存在state里解决这个问题,只有当props更新时,state内的派生数据才会更新。