16.React学习笔记.React更新机制

一. 发生更新的时机以及顺序##


image.png

  1. props/state改变
  2. render函数重新执行
  3. 产生新的VDOM树
  4. 新旧DOM树进行diff
  5. 计算出差异进行更新
  6. 更新到真实的DOM

二. React更新流程##

React将最好的O(n^3)的tree比较算法优化为O(n)。

  • 同层节点之间相互比较,不跨节点。
  • 不同类型的节点,产生不同的树结构:如果该节点不同,会将旧tree中该节点的子树全部删掉。直接生成新的子树,挂载到DOM中。
  • 开发中,可以通过key来指定哪些节点在不同的渲染下保持稳定。

三. 不同情况##

情况一:对比不同类型的元素

当节点为不同的元素,React会拆卸原有的树,并且建立起新的树。

  • 当一个元素改变,会触发一个完整的重建流程。
  • 卸载一棵树时,对应的DOM节点也会被销毁,组件实例将执行componentWillUnmount()方法。
  • 建立一棵新树时,对应得DOM节点会被创建以及插入到DOM中,组件实例将执行componentWillMount()方法,紧接着componentDidMount()方法。
子树销毁,元素不会复用。####

情况二:对比同一类型的元素

  • 当对比两个相同类型的React元素时,React会保留DOM节点,仅比对及更新有改变的属性, 比如下面例子:


image.png

  • React知道只需要修改DOM元素上的className属性。


image.png

-当 更新style属性 时,React仅更新有所改变的属性,没有变化的属性不会变。

  • 如果是同类型的组件元素:组件会保持不变,React会更新该组件的props,并且调用componentWillReceiveProps()和componentWillUpdate()方法;
  • 下一步,调用render()方法,diff算法将在之前的结果以及新的结果中进行递归。

情况三:对子节点进行递归

image.png

  • 默认条件下,当递归DOM节点的子元素时,React会同时遍历两个子元素的列表;当产生差异时,生成一个mutation。
  • 如上图,前两个比较相同,不会有mutation。
  • 最后一个比较,产生一个mutation,将其插入到新的DOM树中即可。
  • 当然这是理想情况


image.png

如果我们在中间插入一条数据:

  • React会对每一个子元素产生一个mutation,而不是保持其不变。
  • 这种方式会有一定的性能问题。

所以这时需要key来优化###

四. key优化##

  1. 在尾部添加数据
  • 有无key意义并不大。
  1. 在前面插入数据
  • 这种情况,在没有key的情况下,所有li都需要进行修改。

  • 当子元素拥有key时,React使用key来匹配原有树上的子元素以及最新树上的子元素:这种情况下:原有的元素只是发生了位移。

    复制代码
    render() {
      return (
        <div>
          <h2>电影列表</h2>
          <ul>
            {
              this.state.movies.map((item,index) => {
                return <li key={item}>{item}</li>
              })
            }
          </ul>
          <button onClick={e => this.insertMovie()}>添加电影</button>
        </div>
      )
    }
  1. key的注意事项:
  • key应该是唯一的。
  • key不要使用随机数(随机数在下一次render时,会重新生成一个数字)。
  • 使用index作为key,对性能是没有优化的,id比较合适。

五. 组件嵌套的render调用##

复制代码
import React, { Component } from 'react'


// Header
function Header() {
  console.log("Header被调用");
  return <h2>我是Header组件</h2>
}

// Banner
class Banner extends Component {
  render() {
    console.log('Banner的render函数被调用');
    return <h3>我是bannner组件</h3>
  }
}

function ProductList() {
  console.log("ProductList被调用");
  return (
    <ul>
      <li>商品列表1</li>
      <li>商品列表2</li>
      <li>商品列表3</li>
      <li>商品列表4</li>
      <li>商品列表5</li>
    </ul>
  )
}
// Main
class Main extends Component {
  render() {
    console.log('Main render函数被调用');
    return (
      <div>
        <Banner />
        <ProductList />
      </div >
    )
  }
}
// Footer
function Footer() {
  console.log("Footer被调用");

  return <h2>我是Footer组件</h2>
}


export default class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      counter: 0,
    }
  }
  render() {
    console.log('App render函数被调用');
    return (
      <div>
        <h2>当前计数:{this.state.counter}</h2>
        <button onClick={e=>this.increment()}>+1</button>
        <Header />
        <Main />
        <Footer />
      </div>
    )
  }
  increment(){
    this.setState({
      counter: this.state.counter + 1
    })
  }
}
  • 调用一个无关的函数,界面改变时,按理来说不应该让别的没有改变的东西重新render。
  • 这个例子中我们在前面插入了一个<h2>和<button>标签,现在点击按钮时全局都会重新渲染。
  • 现在对其进行优化

六. 组件嵌套的render调用的优化##

  • 调用完setState后,不想render时阻断其渲染。

  • 使用shouldComponentUpdate() {}这个生命函数,默认情况下其返回true,也就是重新渲染;手动设置为false后,将不会重新渲染,但不影响初始化的渲染。

  • 我们的目的是:想要阻断时阻断(事件发生后与界面没有依赖),不想阻断时渲染,如下代码。

    复制代码
    shouldComponentUpdate(nextProps, nextState){
      if(this.state.counter !== nextState.counter){
        return true;
      }
      return false;
    }

以上为简单情况,当组件变多后,情况将很复杂,函数/类组件都需要考虑到###

  • 每个类都设置该生命周期函数太麻烦。

  • 我们通过继承PureComponent而不是Component来进行简化,其会对state和props进行比较来决定是否重新render。

  • shouldComponentUpdate在源码中进行更新时,决定是否需要render。

  • 回溯到源码ReactFiberClassComponent中时,有如下方法:

    function checkShouldComponentUpdate(
    workInProgress,
    ctor,
    oldProps,
    newProps,
    oldState,
    newState,
    nextContext,
    ) {
    const instance = workInProgress.stateNode;
    // 判断有无该生命周期函数
    if (typeof instance.shouldComponentUpdate === 'function') {
    startPhaseTimer(workInProgress, 'shouldComponentUpdate');
    // -----------------
    // 核心代码
    const shouldUpdate = instance.shouldComponentUpdate(
    newProps,
    newState,
    nextContext,
    );
    stopPhaseTimer();
    return shouldUpdate;
    }
    // -----------------
    if (ctor.prototype && ctor.prototype.isPureReactComponent) {
    return (
    !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
    );
    }

    复制代码
    return true;

    }

  • 该方法最终返回true/false。

  • 这个对应React中PureComponent的特点

    image.png

  • 其中isPureReactComponent属性对应上面放出来源代码中对原型中isPureReactComponent属性的判断。

  • 如果有isPureReactComponent属性,则对oldProps,oldState和newProps,newState进行一个浅层比较。

  • 通过浅层比较来判断是否发生了改变。

追溯到shallowEqual方法源码中

复制代码
import is from './objectIs';

const hasOwnProperty = Object.prototype.hasOwnProperty;

/**
 * Performs equality by iterating through keys on an object and returning false
 * when any key has values which are not strictly equal between the arguments.
 * Returns true when the values of all keys are strictly equal.
 */
function shallowEqual(objA: mixed, objB: mixed): boolean {
  if (is(objA, objB)) {
    return true;
  }

  if (
    typeof objA !== 'object' ||
    objA === null ||
    typeof objB !== 'object' ||
    objB === null
  ) {
    return false;
  }

  const keysA = Object.keys(objA);
  const keysB = Object.keys(objB);

  if (keysA.length !== keysB.length) {
    return false;
  }

  // Test for A's keys different from B.
  for (let i = 0; i < keysA.length; i++) {
    if (
      !hasOwnProperty.call(objB, keysA[i]) ||
      !is(objA[keysA[i]], objB[keysA[i]])
    ) {
      return false;
    }
  }

  return true;
}

export default shallowEqual;
  • 先判断两个对象,相同则true,返回后取反,表示不需要更新。
  • 接着分别判断两个对象,不是对象或者为null时返回false,强制刷新
  • 然后将两个对象中的keys取出来,若长度不想等则返回false,若相等,则对其中属性进行比较,不相等则返回false进行刷新。

这就回到我们案例中,只有App,Header,Footer的render被调用。

  • PureComponent对props和state进行shallowEqual 。
  • Main,Banner,ProductList没有依赖任何props/state,所以没有重新渲染。
  • 开发中只需要shallowEqual深层比较非常浪费性能。
  • PureComponent可以解决类组件的render调用,但解决不了函数式组件

七. memo的使用,优化函数式组件##

memo为高阶组件。

复制代码
const MemoHeader = memo(function Header() {
  console.log("Header被调用");
  return <h2>我是Header组件</h2>
})


image.png

  • 我们将原来的函数组件传入memo函数中,生成一个新的组件类型。
  • 将Footer也进行转换,这样只有App重新渲染了,
  • 但我们没有更改ProductList,其也没有重新渲染,原因是在Main中,重新渲染已经被阻止了
  • 为了以防万一,也可以用memo优化。

理论上:建议所有类组件都用PureComponent,所有函数组件都包裹memo
© 著作权归作者所有,转载或内容合作请联系作者

喜欢的朋友记得点赞、收藏、关注哦!!!

相关推荐
皮卡祺q4 分钟前
【算法-0】背包问题(三维+二维)
java·javascript·算法
qq_401700417 分钟前
Qt 多线程编程
开发语言·qt
whuhewei18 分钟前
手写Promise
开发语言·javascript·ecmascript
AI科技星21 分钟前
空间圆柱螺旋运动第一性原理终极推导·证明·核验·全量纲闭环
开发语言·人工智能·算法·计算机视觉·量子计算
星幻元宇VR21 分钟前
VR消防安全行走平台打造真实火灾逃生体验
科技·学习·安全·vr·虚拟现实
shinelord明27 分钟前
【云计算】k8sclient API 镜像操作 Java 类封装
java·kubernetes·云计算
invicinble27 分钟前
spring事务相关信息量的沉淀
java·后端·spring
basketball61633 分钟前
C++ 多态完全指南:同一个接口,千变万化的行为
java·开发语言·c++
川冰ICE35 分钟前
JavaScript入门⑤|数组方法全攻略,map/filter/reduce三剑客
开发语言·javascript·ecmascript
晓梦林40 分钟前
kakeru靶场学习笔记
笔记·学习