Skip to content

协调与调度

render阶段作用是什么?

根据一次更新中产生的新状态值,通过 React.createElement ,替换成新的状态,得到新的 React element 对象,新的 element 对象上,保存了最新状态值。 createElement 会产生一个全新的props。到此 render 函数使命完成了。

React 几种控制 render 方法

  • 第一种就是从父组件直接隔断子组件的渲染,经典的就是 memo,缓存 element 对象。
  • 第二种就是组件从自身来控制是否 render ,比如:PureComponent ,shouldComponentUpdate 。

缓存React.element对象

tsx
/*子组件*/
function Children ({ number }){
    console.log('子组件渲染')
    return <div>let us learn React!  { number } </div>
}
/*父组件*/
export default class Index extends React.Component{
    constructor(props){
        super(props)
        this.state={
            numberA:0,
            numberB:0,
        }
        this.component =  <Children number={this.state.numberA} />
    }
    controllComponentRender=()=>{ /*通过此函数判断*/
        const { props } = this.component
        if(props.number !== this.state.numberA ){ /*只有 numberA 变化的时候,重新创建 element 对象*/
            return this.component = React.cloneElement(this.component,{ number:this.state.numberA })
        }
        return this.component
    }
    render(){
       return <div>
          { this.controllComponentRender()  }
          <button onClick={ ()=> this.setState({ numberA:this.state.numberA + 1 }) } >改变numberA</button>
          <button onClick={ ()=> this.setState({ numberB:this.state.numberB + 1 }) }  >改变numberB</button>
       </div>
    }
}
  • 首先把 Children 组件对应的 element 对象,挂载到组件实例的 component 属性下。
  • 通过 controllComponentRender 控制渲染 Children 组件,如果 numberA 变化了,证明 Children的props 变化了,那么通过 cloneElement 返回新的 element 对象,并重新赋值给 component ,如果没有变化,那么直接返回缓存的 component 。

useMemo

tsx
const cacheSomething = useMemo(create,deps)
  • create:第一个参数为一个函数,函数的返回值作为缓存值,如上 demo 中把 Children 对应的 element 对象,缓存起来。
  • deps: 第二个参数为一个数组,存放当前 useMemo 的依赖项,在函数组件下一次执行的时候,会对比 deps 依赖项里面的状态,是否有改变,如果有改变重新执行 create ,得到新的缓存值。
  • cacheSomething:返回值,执行 create 的返回值。如果 deps 中有依赖项改变,返回的重新执行 create 产生的值,否则取上一次缓存值。

useMemo原理

useMemo 会记录上一次执行 create 的返回值,并把它绑定在函数组件对应的 fiber 对象上,只要组件不销毁,缓存值就一直存在,但是 deps 中如果有一项改变,就会重新执行 create ,返回值作为新的值记录到 fiber 对象上。

useMemo应用场景

  • 可以缓存 element 对象,从而达到按条件渲染组件,优化性能的作用。
  • 如果组件中不期望每次 render 都重新计算一些值,可以利用 useMemo 把它缓存起来。
  • 可以把函数和属性缓存起来,作为 PureComponent 的绑定方法,或者配合其他Hooks一起使用。

PureComponent

纯组件是一种发自组件本身的渲染优化策略,当开发类组件选择了继承 PureComponent ,就意味这要遵循其渲染规则。规则就是浅比较 state 和 props 是否相等。

  • 对于 props ,PureComponent 会浅比较 props 是否发生改变,再决定是否渲染组件
  • 对于 state ,如上也会浅比较处理
  • 浅比较只会比较基础数据类型,对于引用类型,比如 demo 中 state 的 obj ,单纯的改变 obj 下属性是不会促使组件更新的,因为浅比较两次 obj 还是指向同一个内存空间,想要解决这个问题也容易,浅拷贝就可以解决,将如上 changeObjNumber 这么修改。这样就是重新创建了一个 obj ,所以浅比较会不相等,组件就会更新了。

PureComponent 原理及其浅比较原则

原型链上会有 isPureReactComponent 属性。

tsx
// react/src/ReactBaseClasses.js
function checkShouldComponentUpdate(){
     if (typeof instance.shouldComponentUpdate === 'function') {
         return instance.shouldComponentUpdate(newProps,newState,nextContext)  /*shouldComponentUpdate 逻辑*/
     }
    if (ctor.prototype && ctor.prototype.isPureReactComponent) {
        return  !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
    }
}
  • isPureReactComponent 就是判断当前组件是不是纯组件的,如果是 PureComponent 会浅比较 props 和 state 是否相等。
  • 还有一点值得注意的就是 shouldComponentUpdate 的权重,会大于 PureComponent。

shallowEqual 是如何浅比较的呢

  • 第一步,首先会直接比较新老 props 或者新老 state 是否相等。如果相等那么不更新组件。
  • 第二步,判断新老 state 或者 props ,有不是对象,或者为 null 的,那么直接返回 false ,更新组件。
  • 第三步,通过 Object.keys 将新老 props 或者新老 state 的属性名 key 变成数组,判断数组的长度是否相等,如果不相等,证明有属性增加或者减少,那么更新组件。
  • 第四步,遍历老 props 或者老 state ,判断对应的新 props 或新 state ,有没有与之对应并且相等的(这个相等是浅比较),如果有一个不对应或者不相等,那么直接返回 false ,更新组件。

shouldComponentUpdate

shouldComponentUpdate 可以根据传入的新的 props 和 state ,或者 newContext 来确定是否更新组件

React.memo

tsx
React.memo(Component,compare)

React.memo 可作为一种容器化的控制渲染方案,可以对比 props 变化,来决定是否渲染组件,React.memo 接受两个参数,第一个参数 Component 原始组件本身,第二个参数 compare 是一个函数,可以根据一次更新中 props 是否相同决定原始组件是否重新渲染。

memo的几个特点是

  • React.memo: 第二个参数 返回 true 组件不渲染 , 返回 false 组件重新渲染。和 shouldComponentUpdate 相反,shouldComponentUpdate : 返回 true 组件渲染 , 返回 false 组件不渲染。
  • memo 当二个参数 compare 不存在时,会用浅比较原则处理 props ,相当于仅比较 props 版本的 pureComponent
  • memo 同样适合类组件和函数组件。

有没有必要在乎组件不必要渲染

在正常情况下,无须过分在乎 React 没有必要的渲染,要理解执行 render 不等于真正的浏览器渲染视图,render 阶段执行是在 js 当中,js 中运行代码远快于浏览器的 Rendering 和 Painting 的,更何况 React 还提供了 diff 算法等手段,去复用真实 DOM 。 什么时候需要注意渲染节流。 但是对于以下情况,值得开发者注意,需要采用渲染节流:

  • 第一种情况数据可视化的模块组件(展示了大量的数据),这种情况比较小心因为一次更新,可能伴随大量的 diff ,数据量越大也就越浪费性能,所以对于数据展示模块组件,有必要采取 memo , shouldComponentUpdate 等方案控制自身组件渲染。
  • 第二种情况含有大量表单的页面,React 一般会采用受控组件的模式去管理表单数据层,表单数据层完全托管于 props 或是 state ,而用户操作表单往往是频繁的,需要频繁改变数据层,所以很有可能让整个页面组件高频率 render 。
  • 第三种情况就是越是靠近 app root 根组件越值得注意,根组件渲染会波及到整个组件树重新 render ,子组件 render ,一是浪费性能,二是可能执行 useEffect ,componentWillReceiveProps 等钩子,造成意想不到的情况发生。

一些开发中的细节问题

  • 开发过程中对于大量数据展示的模块,开发者有必要用 shouldComponentUpdate ,PureComponent来优化性能。
  • 对于表单控件,最好办法单独抽离组件,独自管理自己的数据层,这样可以让 state 改变,波及的范围更小。
  • 如果需要更精致化渲染,可以配合 immutable.js 。
  • 组件颗粒化,配合 memo 等 api ,可以制定私有化的渲染空间。

useCallback 和 useMemo 有什么区别?

  • useCallback 第一个参数就是缓存的内容,
  • useMemo 需要执行第一个函数,返回值为缓存的内容,
  • 比起 useCallback , useMemo 更像是缓存了一段逻辑,或者说执行这段逻辑获取的结果。那么对于缓存 element 用 useCallback 可以吗,答案是当然可以了。

Welcome to the site