对Component与PureComponent的理解

在React写类组件的时候,类组件都会extends继承React.PureComponent或者React.Component,那为什么要继承React.PureComponent或者React.Component呢?,我们都知道PureComponentComponent都是React中的组件类,它们都可以用来写类组件,那它们的作用是什么呢?它们又有什么区别呢?

Component

Component是React中定义组件的基类,它的shouldComponentUpdate方法默认返回true,也就是说,每次调用setState方法都会引发组件重新渲染。如果希望在一些情况下不重新渲染组件,就需要在继承自Component的组件中手动实现shouldComponentUpdate方法来进行比较。

下面来看一段代码:

class Child extends React.Component{
  state = {
    count:0
  }
  // 点击改变状态了
  changeCount =  ()  =>  {
    let { count } =  this.state
    this.setState({
      count:++count
    })
  }
  // 点击按钮状态只是赋了相同的值,并没有改变
  constCount =  () =>  {
    this.setState({
      count:this.state.count
    })
  }
  render(){
    console.log('执行了---render')
    let { count } =  this.state
    return <div className="child">
      <div>当前状态: { count }</div>
      <div><button onClick={this.changeCount.bind(null)}>改变状态</button></div>
      <div><button onClick={this.constCount.bind(null)}>不改变状态</button></div>
    </div>
  }
}

上面代码每次点击"改变状态"按钮,都会触发changeCount方法通过setState改变状态,每次点击都触发了render函数;然而当点击"不改变状态"按钮,都会触发constCount方法通过setState重新给count赋值且并没有改变值的状态,在这种情况下每次点击按钮也触发了render函数,像这种情况,可以考虑在没有状态改变的情况下,不用触发render函数,从而减少页面渲染优化性能。

通过更新前后的state值是否相同来判断是否需要执行render函数:

class Child extends React.Component{
  state = {
    count:0
  }
  // 点击改变状态了
  changeCount =  ()  =>  {
    let { count } =  this.state
    this.setState({
      count:++count
    })
  }
  // 点击按钮状态只是赋了相同的值,并没有改变
  constCount =  () =>  {
    this.setState({
      count:this.state.count
    })
  }
  // 通过更新前后的state值是否相同来判断是否需要执行render函数
  shouldComponentUpdate(props,state){
    console.log(props,state)
    let { count } =  this.state
    if(state.count === count){
      return false
    }
    return true
  }
  render(){
    console.log('执行了---render')
    let { count } =  this.state
    return <div className="child">
      <div>当前状态: { count }</div>
      <div><button onClick={this.changeCount.bind(null)}>改变状态</button></div>
      <div><button onClick={this.constCount.bind(null)}>不改变状态</button></div>
    </div>
  }
}

上面代码增加了个shouldComponentUpdate函数,函数包含两个参数,第一个是即将更新的 props值,第二个是即将更新后的state值,这里通过判断state的值是否和组件状态里面的count的值是否相等来返回falsetrue,如果相等就返回false就不更新组件,也就不执行render函数;如果不相等就返回true就需要更新组件,执行render函数重新渲染页面,这样就减少了页面不必要的渲染。

小结:这里组件继承了React.Component需要手动实现shouldComponentUpdate函数来对比状态是否改变,从而判断是否需要执行render函数重新渲染页面。

PureComponent

PureComponent则提供了一种基于浅比较的优化机制,它默认实现了shouldComponentUpdate方法,会自动对组件的propsstate进行浅比较,如果发现propsstate没有发生变化,则阻止组件的重新渲染。

看下面这段代码:

class Child extends React.PureComponent{
  state = {
    count:0
  }
  // 点击改变状态了
  changeCount =  ()  =>  {
    let { count } =  this.state
    this.setState({
      count:++count
    })
  }
  // 点击按钮状态只是赋了相同的值,并没有改变
  constCount =  () =>  {
    this.setState({
      count:this.state.count
    })
  }
  render(){
    console.log('执行了---render')
    let { count } =  this.state
    return <div className="child">
      <div>当前状态: { count }</div>
      <div><button onClick={this.changeCount.bind(null)}>改变状态</button></div>
      <div><button onClick={this.constCount.bind(null)}>不改变状态</button></div>
    </div>
  }
}

上面代码当点击"改变状态"按钮,都会触发changeCount方法通过setState改变状态,每次点击都触发了render函数;然而当点击"不改变状态"按钮,都会触发constCount方法通过setState重新给count赋值且并没有改变值的状态,在这种情况下每次点击按钮后render函数并没有执行,这是因为在PureComponent方法里默认实现了shouldComponentUpdate方法,会自动对组件的propsstate进行浅比较,如果发现propsstate没有发生变化,则不会执行render函数,也就不会使组件重新渲染。

如果深层的状态也只是赋个相同的值,会执行render函数?

class Child extends React.PureComponent{
  state = {
    count:0,
    obj:{
      num:0
    }
  }
  // 点击啊按钮只是赋了相同的值深层的状态
  changeNum =  ()  =>  {
    let { obj } =  this.state
    this.setState({
      obj:{
        num: obj.num
      }
    })
  }
  render(){
    console.log('执行了---render')
    let { obj } =  this.state
    return <div className="child">
      <div>当前状态num: { obj.num }</div>
      <div><button onClick={this.changeNum.bind(null)}>深层的状态</button></div>
    </div>
  }
}

上面代码当点击深层的状态按钮时,也只是给num赋了相同的值不做改变,然而却执行render函数,也就是说PureComponent做了浅比较。

PureComponent是怎么去做浅比较的呢?

// 检测是否为对象
const isObject = function(obj){
  return obj !== null && /^(object|function)$/.test(typeof obj)
}
// 浅比较函数
function shallowEqual(objA, objB){
  if(!isObject(objA) || !isObject(objB)) return false
  if(objA === objB) return true
  const keysA = Object.keys(objA);
  const keysB = Object.keys(objB);
  // 比较key的数量
  if (keysA.length !== keysB.length) return false
  // 两个对象key的数量一致,逐一比较内部成员
  for (let i = 0; i < keysA.length; i++) {
    let key = keysA[i]
    //判断objB中是否存在objA的key; 如果存在相同的key,判断key对应的值是否相等
    if(!Object.hasOwnProperty.call(objB,key) || !Object.is(objA[key], objB[key])){
      return false
    }
  }
  return true
}
let k = [1,2]
let a  = {
  a:1,
  b:k
}
let b  = {
  a:1,
  b:k
}
console.log(shallowEqual(a,b)) // true

以上就是PureComponent相当于做了这样的一个浅对比的事情,如果是深层的就不会再去比较了,比如:

//......
let a  = {
  a:1,
  b:[1,2]
}
let b  = {
  a:1,
  b:[1,2]
}
console.log(shallowEqual(a,b))  //  false

如果改成以上代码就不相等了,虽然b的值都是相同的数组,但在栈中却是不同的引用指针。

总结

PureComponent其实就是一个继承自Component的子类,会自动加载shouldComponentUpdate函数。当组件需要更新的时候,shouldComponentUpdate会对组件的propsstate进行一次浅比较。如果propsstate都没有发生变化,那么render方法也就不会触发,在react性能方面得到了优化。


 上一篇
React的类组件ref使用 React的类组件ref使用
React提供了ref属性可以让我们可以引用组件的实例,其实就是ReactDOM.render()返回的组件实例,可以通过ref来获取DOM元素。 初次使用 在React中,ref可以挂载到html元素上,同时也可以挂载在React元素上,
2022-04-09
下一篇 
React类组件的生命周期 React类组件的生命周期
组件的生命周期 组件从先创建,然后挂载到页面中运行,最后组件不用时卸载的过程,这个过程就叫做组件的生命周期。而在这个过程中,React提供了在适当的时候会自动执行的不同的函数,我们称之为生命周期函数,也称钩子函数。组件的生命周期分为三个阶段
2022-04-08
  目录