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
© 著作权归作者所有,转载或内容合作请联系作者

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

相关推荐
The Future is mine22 分钟前
Python计算经纬度两点之间距离
开发语言·python
Enti7c23 分钟前
HTML5和CSS3的一些特性
开发语言·css3
腥臭腐朽的日子熠熠生辉28 分钟前
解决maven失效问题(现象:maven中只有jdk的工具包,没有springboot的包)
java·spring boot·maven
爱吃巧克力的程序媛30 分钟前
在 Qt 创建项目时,Qt Quick Application (Compat) 和 Qt Quick Application
开发语言·qt
ejinxian30 分钟前
Spring AI Alibaba 快速开发生成式 Java AI 应用
java·人工智能·spring
杉之35 分钟前
SpringBlade 数据库字段的自动填充
java·笔记·学习·spring·tomcat
圈圈编码1 小时前
Spring Task 定时任务
java·前端·spring
俏布斯1 小时前
算法日常记录
java·算法·leetcode
独好紫罗兰1 小时前
洛谷题单3-P5719 【深基4.例3】分类平均-python-流程图重构
开发语言·python·算法
27669582921 小时前
美团民宿 mtgsig 小程序 mtgsig1.2 分析
java·python·小程序·美团·mtgsig·mtgsig1.2·美团民宿