React@16.x(21)渲染流程-更新

打印 上一主题 下一主题

主题 517|帖子 517|积分 1551

上篇文章先容了首次渲染时,React 做的事变。
这篇先容下在是如何更新节点的。
  1,更新的2种场景


  • 重新调用 ReactDOM.render(),触发根节点更新。
  • 调用类组件实例的 this.setState(),导致该实例所在的节点更新。
2,节点更新

第1种情况,直接进入根节点的对比 diff 更新
第2种情况,调用this.setState()的更新流程:

  • 运行生命周期函数 static getDerivedStateFromProps;
  • 运行生命周期函数 shouldComponentUpdate,如果返回 false,则到此竣事,终止流程
  • 运行 render,得到一个新的节点,进入该节点的对比 diff 更新
  • 将生命周期函数 getSnapshotBeforeUpdate加入执行队列,以待将来执行
  • 将生命周期函数 componentDidUpdate加入执行队列,以待将来执行。
后续步骤

  • 更新假造DOM树;
  • 完成真实DOM更新;
  • 依次调用执行队列中的 componentDidMount
  • 依次调用执行队列中的 getSnapshotBeforeUpdate
  • 依次调用执行队列中的 componentDidUpdate
   注意,这里的 componentDidMount 指的是子组件的,但子组件也不肯定会执行(重新挂载)。
别的,涉及到的生命周期函数的执行顺序时,注意父 render 执行完后遍历进入子组件,当子组件的全部生命周期函数执行后,才会跳出循环继承执行父的其他生命周期函数。
  3,对比 diff 更新

整体流程:将运行 render 产生的新节点,对比旧假造DOM树中的节点,发现差别并完成更新。
题目:如何确定,对比旧假造DOM中的哪个节点?
3.1,React 的假设

React 为了进步对比效率,会做以下假设:

  • 节点不会出现层级移动,这样可以直接在旧树中找到对应位置的节点举行对比。
  • 不同的节点类型,会天生不同的结构。节点类型指 React 元素的 type 值。
  • 多个兄弟节点,通过 key 做唯一标识,这样可以确定要对比的新节点。 如果没有 key,则按照顺序举行对比。
3.1.2,key

如果某个旧节点有 key 值,则它在更新时,会寻找雷同层级中雷同 key 的节点举行对比。
所以,key 值应该在肯定范围内(一样平常为兄弟节点之间)保持唯一,并保持稳定
   保持稳定:不能随意更改,比如通过随机数天生,更新后随机数发生厘革找不到旧值。(有意为之须要每次都使用新节点的情况除外)
  2.1,找到了对比的目标

2.1.1,节点类型一致

根据不同的节点类型,做不同的事变:
1,空节点

无事发生。
2,DOM节点


  • 直接重用之前的真实DOM对象,
  • 属性的厘革会记载下来,以待将来同一举行更新(此时不会更新),
  • 遍历该DOM节点的子节点,递归对比 diff 更新
3,文本节点


  • 直接重用之前的真实DOM对象,
  • 将新文本(nodeValue)的厘革记载下来,以待将来同一举行更新。
4,组件节点

1,函数组件

重新调用函数得到新一个新节点对象,递归对比 diff 更新
2,类组件


  • 重用之前的实例;
  • 运行生命周期函数 static getDerivedStateFromProps;
  • 运行生命周期函数 shouldComponentUpdate,如果返回 false,则到此竣事,终止流程
  • 运行 render,得到一个新的节点,进入该节点的递归对比 diff 更新
  • 将生命周期函数 getSnapshotBeforeUpdate加入执行队列,以待将来执行
  • 将生命周期函数 componentDidUpdate加入执行队列,以待将来执行。
5,数组节点

遍历数组,递归对比 diff 更新
2.1.2,节点类型不一致

卸载旧节点,使用新节点。
1,类组件节点
直接放弃,并运行生命周期函数 componentWillUnmount,再递归卸载子节点。
2,其他节点
直接放弃,如果该节点有子节点,递归卸载子节点。
2.2,没有找到对比的目标

有2种情况:


  • 新的DOM树中有节点被删除,则卸载多余的旧节点。
  • 新的DOM树中有节点添加,则创建新加入的节点。
4,举例

例1,组件节点类型不一致

更新时如果节点类型不一致,那全部的子节点全部卸载,重新更新
不管子节点的类型是否一致。所以如果是类组件,会重新挂载并运行 componentDidMount。
下面的例子中,就是因为节点类型发生厘革 div --> p,所以当点击按钮切换时,子组件 Child 会重新挂载(3个生命周期函数都会执行),并且 button 也不是同一个。
  1. import React, { Component } from "react";
  2. export default class App extends Component {
  3.     state = {
  4.         visible: false,
  5.     };
  6.     changeState = () => {
  7.         this.setState({
  8.             visible: !this.state.visible,
  9.         });
  10.     };
  11.     render() {
  12.         if (this.state.visible) {
  13.             return (
  14.                 <div>
  15.                     <Child />
  16.                     <button onClick={this.changeState}>toggle</button>
  17.                 </div>
  18.             );
  19.         } else {
  20.             return (
  21.                 <p>
  22.                     <Child />
  23.                     <button onClick={this.changeState}>toggle</button>
  24.                 </p>
  25.             );
  26.         }
  27.     }
  28. }
  29. // 子组件
  30. class Child extends Component {
  31.     state = {};
  32.     static getDerivedStateFromProps() {
  33.         console.log("子 getDerived");
  34.         return null;
  35.     }
  36.     componentDidMount() {
  37.         console.log("子 didMount");
  38.     }
  39.     render() {
  40.         console.log("子 render");
  41.         return <span>子组件</span>;
  42.     }
  43. }
复制代码
例2,子节点结构发生厘革

根节点类型一致,子节点结构发生厘革。
下面的例子中,节点对比是按照顺序的,参考上文提到的React的假设1和假设3。
所以,当点击出现 h1 元素的节点对比更新过程中,

  • 对比组件根节点 div,类型一致重用之前的真实DOM对象,遍历子节点。
  • 新节点 h1 会和原来这个位置的旧节点 button 对比,不一致则删除旧节点 button。
  • 新节点 button 发现没有找到对比的目标,则没有其他操纵。
  • 通过新假造DOM树,完成真实DOM更新。
  1. export default class App extends Component {
  2.     state = {
  3.         visible: false,
  4.     };
  5.     changeState = () => {
  6.         this.setState({
  7.             visible: !this.state.visible,
  8.         });
  9.     };
  10.     render() {
  11.         if (this.state.visible) {
  12.             return (
  13.                 <div>
  14.                     <h1>标题1</h1>
  15.                     <button className="btn" onClick={this.changeState}>
  16.                         toggle
  17.                     </button>
  18.                 </div>
  19.             );
  20.         } else {
  21.             return (
  22.                 <div>
  23.                     <button className="btn" onClick={this.changeState}>
  24.                         toggle
  25.                     </button>
  26.                 </div>
  27.             );
  28.         }
  29.     }
  30. }
复制代码
所以,一样平常须要改变DOM 结构时,为了提升效率,要么指定 key来直接告诉 React 要对比的旧节点,要么保证顺序和层级一致。
上面的例子可以更改如下,这也是空节点的作用之一
  1. render() {
  2.    return (
  3.         <div className="parent">
  4.             {this.state.visible && <h1>标题1</h1>}
  5.             <button className="btn" onClick={this.changeState}>
  6.                 toggle
  7.             </button>
  8.         </div>
  9.     );
  10. }
  11. // 或
  12. render() {
  13.    return (
  14.         <div className="parent">
  15.             {this.state.visible ? <h1>标题1</h1> : null}
  16.             <button className="btn" onClick={this.changeState}>
  17.                 toggle
  18.             </button>
  19.         </div>
  20.     );
  21. }
复制代码
例3,key 的作用

下面的例子,子组件是类组件,有自己的状态,也会更改状态。
父组件以数组的形式渲染多个子组件,同时会在数组头部插入新的子组件。
  1. import React, { Component } from "react";
  2. class Child extends Component {
  3.     state = {
  4.         num: 1,
  5.     };
  6.     componentDidMount() {
  7.         console.log("子 didMount");
  8.     }
  9.     componentWillUnmount() {
  10.         console.log("子组件卸载");
  11.     }
  12.     changeNum = () => {
  13.         this.setState({
  14.             num: this.state.num + 1,
  15.         });
  16.     };
  17.     render() {
  18.         return (
  19.             <div>
  20.                 <span>数字:{this.state.num}</span>
  21.                 <button onClick={this.changeNum}>加一</button>
  22.             </div>
  23.         );
  24.     }
  25. }
  26. export default class App extends Component {
  27.     state = {
  28.         arr: [<Child />, <Child />],
  29.     };
  30.     addArr = () => {
  31.         this.setState({
  32.             arr: [<Child />, ...this.state.arr],
  33.         });
  34.     };
  35.     render() {
  36.         return (
  37.             <div className="parent">
  38.                 {this.state.arr}
  39.                 <button className="btn" onClick={this.addArr}>
  40.                     添加
  41.                 </button>
  42.             </div>
  43.         );
  44.     }
  45. }
复制代码
结果:

会发现,新的子组件加到最后去了,同时会打印一次 子 didMount,并且 componentWillUnmount 并没有执行。
原因:因为没有设置 key,所以在新旧节点对比时,发现第1个节点类型一致,于是重用了之前的实例。直到对比到最后一个发现没有找到对比目标,才会用新的节点来创建真实DOM。
   别的,正因为是类组件节点,所以并不会像我们印象中数组没有指定 key 时,如果往数组的开头插入元素,会导致全部的数组元素重新渲染。
  增加 key 调整:
  1. export default class App extends Component {
  2.     state = {
  3.         arr: [<Child key={1} />, <Child key={2} />],
  4.         nextId: 3,
  5.     };
  6.     addArr = () => {
  7.         this.setState({
  8.             arr: [<Child key={this.state.nextId} />, ...this.state.arr],
  9.             nextId: this.state.nextId + 1,
  10.         });
  11.     };
  12.     render() {
  13.         return (
  14.             <div className="parent">
  15.                 {this.state.arr}
  16.                 <button className="btn" onClick={this.addArr}>
  17.                     添加
  18.                 </button>
  19.             </div>
  20.         );
  21.     }
  22. }
复制代码

以上。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

农妇山泉一亩田

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表