React篇

143 阅读23分钟

说一说对 react 的理解

  • 思路
    • 是什么
    • 能干什么 用途和应用场景
    • 如何实现的 核心原理
    • 效果怎么样 优缺点
  • 是什么
    • React 是一个用于构建用户界面的 javascript 库
  • 能干什么
    • 可以通过组件化的方式构建快速响应的大型 web 应用程序
  • 如何实现
    • 声明式渲染
    • 组件化
  • 优缺点
    • 优点
      • 开发团队和社区强大
      • 多元化 可以编写多个程序 比如 ios android VR 和命令
      • API 比较简单
    • 缺点
      • 没有官方系统解决方案 选型成本高
      • 过于灵活 不容易写出高质量的应用

为什么 react 要引入 jsx 或者说 jsx 的作用是什么

  • jsx 其实是 react.createElement 的语法糖
  • 目的是为了实现
    • 声明式渲染
    • 让代码结构可以非常清晰 可读性较强
    • HTML js css 分离 能实现高内聚低耦合 方便重用和组合
    • 不会引入新的概念和语法 会 js 就会写 react

说一下对虚拟节点的理解

  • 优点
    • 处理了浏览器兼容问题,避免用户真实操作 dom
    • 内容经过了 xss 处理 防止了 xss 攻击
    • 容易实现跨平台开发
    • 更新的时候可以实现差异化更新,避免更新 dom 操作
  • 缺点
    • 虚拟 DOM 需要消耗额外的内存
    • 首次渲染不一定会更快

函数组件和类组件的区别

  • 编程思想不同
    • 类组件需要创建实例,是基于面向对象的方式编程 而函数式组件不需要创建实例,接收输入,返回输出,是基于函数式编程
  • 内存占用
    • 类组件需要创建保存实例,会占用一定的内存空间,函数组件不需要实例,可以节约内存占用
  • 捕获特性
    • 函数组件具有值捕获特性
  • 可测试性
    • 函数式组件更方便编写单元测试
  • 状态
    • 类组件有自己的实例,可以定义状态,而且可以修改状态。函数式组件里之前没有状态,但是现在可以通过 useState 来创建
  • 生命周期
    • 类组件有自己的生命周期,可以在生命周期内编写逻辑,函数组件没有生命周期 但是可以通过 useEffect 来实现相应的效果
  • 逻辑复用
    • 类组件是通过继承的方式, 函数组件是通过自定义 Hooks 来实现 且函数组件比类组件复用的优先级高
  • 跳过更新
    • 类组件是通过 shouldComponentUpdate 和 PureCompone 来跳过更新,而函数组件是通过 memo 来跳过更新
  • 发展前景
    • 函数式必定会成为主流, 因为它可以更好的规避 this,规范和复用,更好的适合时间分片和并发渲染

react 的渲染过程

  • 虚拟 dom+ 渲染可中断(fiber)

  • 之前遇到的问题 js 任务执行时间过长

    • 浏览器刷新频率为 60Hz,大概 16.6 毫秒渲染一次,而 JS 线程和渲染线程是互斥的,所以如果 JS 线程执行任务时间超过 16.6ms 的话,就会导致掉帧,导致卡顿,解决方案就是 React 利用空闲的时间进行更新,不影响渲染进行的渲染
    • 把一个耗时任务切分成一个个小任务,分布在每一帧里的方式就叫时间切片

Fiber

  • 为什么出现 fiber
  • dom 更新都是从根节点开始逐级查阅 react15 之前使用的是队列 没办法中断 如果嵌套层数过多 会影响后续渲染 出现卡顿现象

帧的概念

  • 目前大多数设备的屏幕刷新率为 60 次/秒
  • 当每秒绘制的帧数(FPS)达到 60 时,页面是流畅的,小于这个值时,用户会感觉到卡顿 16.66ms
  • 每个帧的开头包括样式计算、布局和绘制
  • JavaScript 执行 Javascript 引擎和页面渲染引擎在同一个渲染线程,GUI 渲染和 Javascript 执行两者是互斥的
  • 如果某个任务执行时间过长,浏览器会推迟渲染
  • 一帧的事件队列
    1. 输入事件
      • Blocking inut events 阻塞输入事件 如 touch wheel
      • Non-blocking inut events 非阻塞输入事件 如 click keypress
    2. javascript 宏任务
      • 定时器
    3. Begin frame
      • window.resize
      • pre frame events
      • scroll
      • media query change
    4. requestAnimationFrame
      • requestAnimationFrame callbacks
    5. Layout
      • recalculate style 计算样式
      • update Layout 更新布局
    6. paint
      • record
      • paint invaildation
      • compositing update
    7. idle peroid 空闲阶段
  • 一帧的任务队列

什么是 fiber

  • 本质还是一个 js 对象 通过 js 对象的不同定义来解释成不同的东西
  • 我们可以通过某些调度策略合理分配 CPU 资源,从而提高用户的响应速度
  • 通过 Fiber 架构,让自己的协调过程变成可被中断。 适时地让出 CPU 执行权,除了可以让浏览器及时地响应用户的交互
  • Fiber 是一个执行单元,每次执行完一个执行单元, React 就会检查现在还剩多少时间,如果没有时间就将控制权让出去
  • Fiebr 是一个数据结构 React 目前的做法是使用链表,每个虚拟节点内部表示为一个 Fiber

requestAnimationFrame

  • requestAnimationFrame 回调函数会在绘制之前执行
  • requestAnimationFrame(callback) 会在浏览器每次重绘前执行 callback 回调, 每次 callback 执行的时机都是浏览器刷新下一帧渲染周期的起点上
  • requestAnimationFrame(callback) 的回调 callback 回调参数 timestamp 是回调被调用的时间,也就是当前帧的起始时间
  • rAfTime performance.timing.navigationStart + performance.now() 约等于 Date.now()
  • performance.now()是页面渲染的时间

requestIdleCallback

  • requestIdleCallback 使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应
  • 正常帧任务完成后没超过 16 ms,说明时间有富余,此时就会执行 requestIdleCallback 里注册的任务
  • requestAnimationFrame 的回调会在每一帧确定执行,属于高优先级任务,而 requestIdleCallback 的回调则不一定,属于低优先级任务 如果别的任务用时超过了 16ms 他就不会执行
  • callback:回调即空闲时需要执行的任务,该回调函数接收一个 IdleDeadline 对象作为入参。其中 IdleDeadline 对象包含:
    • didTimeout,布尔值,表示任务是否超时,结合 timeRemaining 使用
    • timeRemaining(),表示当前帧剩余的时间,也可理解为留给任务的时间还有多少
  • options:目前 options 只有一个参数
    • timeout。表示超过这个时间后,如果任务还没执行,则强制执行,不必等待空闲

MessageChannel

  • 由于 requestIdleCallback 只有 chorme 支持 所以我们要自己模拟一个 requestIdleCallback 出来
  • MessageChannel API 允许我们创建一个新的消息通道,并通过它的两个 MessagePort 属性发送数据
  • MessageChannel 创建了一个通信的管道,这个管道有两个端口,每个端口都可以通过 postMessage 发送数据,而一个端口只要绑定了 onmessage 回调方法,就可以接收从另一个端口传过来的数据
  • MessageChannel 是一个宏任务
  • postMessage 发送消息 onmessage 接收消息

Fiber 执行阶段

  • 每次渲染有两个阶段:Reconciliation(协调 render 阶段)和 Commit(提交阶段)
    • 协调阶段: 可以认为是 Diff 阶段, 这个阶段可以被中断, 这个阶段会找出所有节点变更,例如节点新增、删除、属性变更等等, 这些变更 React 称之为副作用(Effect)
    • 提交阶段: 将上一个阶段计算出来的需要处理的副作用(Effects)一次性执行了。这个阶段必须同步执行,不能被打断
  • render 算法 二叉树的反序
    • 从顶点开始遍历
    • 如果有第一个儿子,先遍历第一个儿子
    • 如果没有第一个儿子,标志着此节点遍历完成
    • 如果有弟弟遍历弟弟
    • 如果有没有下一个弟弟,返回父节点标识完成父节点遍历,如果有叔叔遍历叔叔
    • 没有父节点遍历结束
    • 先儿子,后弟弟,再叔叔,辈份越小越优先
    • 什么时候一个节点遍历完成? 没有子节点,或者所有子节点都遍历完成了
    • 没爹了就表示全部遍历完成了
  • Commit 过程
  • 给每一个 Fiber 执行单元 加一些标识 形成链表 firstEffect 标识子级第一个副作用 nextEffect 标识下一个副作用 lastEffect 标识子级最后一个副作用
  • render 过程
  • commit 链表

fiber 类型

  • 根 fiber 整个 fiber 树最顶部的一个节点节点
  • 元素节点 fiber div span a 等元素节点的 fiber
  • 文本类型 fiber 文字类型
  • 类组件 fiber 类组件
  • 函数组件 fiber 函数组件

fiber 流程

  • 从根节点开始渲染和调度 有两个阶段
  • render 阶段
    • 也叫 diff 阶段 对比新旧的虚拟 DOM 进行增量 更新 或者删除
    • 这个阶段比较花费时间, 因此我们需要对任务进行拆分,拆分的最小维度是一个虚拟 dom 节点 此阶段可以暂停 因为用的是链表
    • 这个阶段的成果有两个 1. 根据虚拟 dom 树构建出 fiber 树 2. 收集 effectList(副作用依赖) 发生变化了的虚拟 dom 节点队列
  • commit 阶段
    • 进行 dom 更新创建 此阶段不能暂停

双缓冲机制

  • fiber 树的对比 diff 是通过对比新旧树 如果每次更新都生成一个新的 fiber 树 随着更新次数的增加 树的数量也就越多 性能就会越差 所以有了双缓冲机制
  • 第三次对比的时候 使用的是第一次渲染的树 只是把树上的节点都改成最新的了 不会增加树的数量

流程图

[fiber 更新]](limingshuai.top/assets/pdf/…)

diff 算法

  • React17+中 DOM-DIFF 就是根据老的 fiber 树和最新的 fiber 树对比生成新的 fiber 树的过程
  • 优化原则
    • 只对同级节点进行对比,如果 DOM 节点跨层级移动,则不会复用
    • 不同类型的元素会产出不同的结构,会销毁老节点,创建新节点
    • 可以通过 key 标识移动的元素
  • 单节点
    • 单节点遍历图
  • 多节点
    • 如果新的节点有多个节点的话
    • 节点有可能更新、删除、新增
    • 多节点的时候会经历二轮遍历
    • 第一轮主要是处理节点的更新,更新包括属性和类型的更新
    • 第二轮处理节点的新增、删除、移动
    • 移动原则是尽量少移动,如果必须有一个要动,新地位高的不动,新地位低的动
    • diff算法结构

合成事件

  • React 把事件委托到 document 对象上
  • 当真实 DOM 元素触发事件,先处理原生事件,然后会冒泡到 document 对象后,再处理 React 事件
  • React 事件绑定的时刻是在 reconciliation 阶段,会在原生事件的绑定前执行
  • 目的和优势
    • 进行浏览器兼容,React 采用的是顶层事件代理机制,能够保证冒泡一致性
    • 事件对象可能会被频繁创建和回收,因此 React 引入事件池,在事件池中获取或释放事件对象(React17 中被废弃)
  • React17 后的改动
    • 事件系统变更
      • 更改事件委托
        • 更改事件委托节点
          • 在 16 版本中,React 都会把事件绑定到页面的 document 元素上,这在多个 React 版本共存的情况下就会虽然某个节点上的函数调用了 event.stopPropagation(),但还是会导致另外一个 React 版本上绑定的事件没有被阻止触发所以在 17 版本中会把事件绑定到 render 函数的节点上
        • 去除事件池
          • 17 版本中移除了事件对象池,这是因为 React 在旧浏览器中重用了不同事件的事件对象,以提高性能,并将所有事件字段在它们之前设置为 null。在 React 16 及更早版本中,使用者必须调用 event.persist() 才能正确的使用该事件,或者正确读取需要的属性

React 的优先级

  • 事件优先级
    • 连续事件 canplay error 的优先级最高 为 2
    • 用户阻塞事件 deag scroll mouseover 等 连续触发,阻塞渲染 优先级为 1
    • 离散事件 click keydown 等 优先级为 0
  • 更新优先级
    • setState 本质上是调用 enqueueSetState,生成一个 update 对象,这时候会计算它的更新优先级,即 update.lane
    • 首先找出 Scheduler 中记录的优先级 schedulerPriority,然后计算更新优先级
  • 任务优先级
    • update 会被一个 React 的更新任务执行
    • 任务优先级被用来区分多个更新任务的紧急程度
    • 收敛同等优先级的任务调度
    • 高优先级任务及时响应
  • 调度优先级
    • 一旦任务被调度,那么它就会进入 scheduler
    • 在 Scheduler 中,这个任务会被包装一下,生成一个属于 Scheduler 自己的 task,这个 task 持有的优先级就是调度优先级

hooks

  • hooks渲染图

react18 新特性

并发模式

  • Concurrent 模式是一组 React 的新功能,可帮助应用保持响应,并根据用户的设备性能和网速进行适当的调整
  • 在 Concurrent 模式中,渲染不是阻塞的。它是可中断的
  • 更新优先级
    • 以前更新没有优先级的概念,优先级高的更新并不能打断之前的更新,需要等前面的更新完成后才能进行
    • 用户对不同的操作对交互的执行速度有不同的预期,所以我们可以根据用户的预期赋予更新不同的优先级
      • 高优先级 用户输入 窗口缩放和拖拽事件
      • 低优先级 数据请求和下载文件
    • 高优先级的更新会中断低优先级的更新
    • 低优先级更新会基于高优先级的结果重新更新

双缓冲

  • 当数据量很大时,绘图可能需要几秒钟甚至更长的时间,而且有时还会出现闪烁现象,为了解决这些问题,可采用双缓冲技术来绘图
  • 双缓冲即在内存中创建一个与屏幕绘图区域一致的对象,先将图形绘制到内存中的这个对象上,再一次性将这个对象上的图形拷贝到屏幕上,这样能大大加快绘图的速度
  • 对于 IO-bound 的更新 (例如从网络加载代码或数据),并发意味着 React 甚至可以在全部数据到达之前就在内存中开始渲染,然后跳过令人不愉快的空白加载状态

批量更新

  • 从队列中加了一个 queueMicrotask(setTimeout)
  • 在 React 18 之前,我们只在 React 事件处理程序期间批量更新。默认情况下,Promise、setTimeout、本机事件处理程序或任何其他事件内部的更新不会在 React 中批处理。
  • 从 React 18 开始 createRoot,所有更新都将自动批处理,无论它们来自何处。
  • 由于所有更新 setTimeout 都是批处理的,所以 React 不会 setState 同步渲染第一次的结果——渲染发生在下一个浏览器滴答声中 在异步事件里 setState 里不会再同步获取最新的值
  • 可以使用 ReactDOM.flushSync 强制更新

suspense 异步加载组件

  • Suspense 让你的组件在渲染之前进行等待,并在等待时显示 fallback 的内容
  • Suspense 内的组件子树比组件树的其他部分拥有更低的优先级
  • 执行过程
    • 在 render 函数中我们可以使用异步请求数据
    • react 会从我们缓存中读取这个缓存
    • 如果有缓存了,直接进行正常的 render
    • 如果没有缓存,那么会抛出一个 promise 异常
    • 当这个 promise 完成后(比发请求数据完成),react 会继续回到原来的 render 中,把数据 render 出来
    • 完全同步写法,没有任何异步 callback 之类的东西
  • React 提供了一个内置函数 componentDidCatch,如果 render() 函数抛出错误,则会触发该函数
  • ErrorBoundary(错误边界)是一个组件,该组件会捕获到渲染期间(render)子组件发生的错误,并有能力阻止错误继续传播

SuspenseList

  • SuspenseList 通过编排向用户显示这些组件的顺序,来帮助协调许多可以挂起的组件
  • revealOrder (forwards, backwards, together) 定义了 SuspenseList 子组件应该显示的顺序
    • together 在所有的子组件都准备好了的时候显示它们,而不是一个接着一个显示
    • forwards 从前往后显示
    • backwards 从后往前显示
  • tail (collapsed, hidden) 指定如何显示 SuspenseList 中未加载的项目
    • 默认情况下,SuspenseList 将显示列表中的所有 fallback
    • collapsed 仅显示列表中下一个 fallback
    • hidden 未加载的项目不显示任何信息

startTransition

  • startTransition 是一个接受回调的函数。我们用它来告诉 React 需要推迟的 state
  • 允许组件将速度较慢的数据获取更新推迟到随后渲染,以便能够立即渲染更重要的更新

useDeferredValue

  • 返回一个延迟响应的值
  • 在 useDeferredValue 内部会调用 useState 并触发一次更新,但此更新的优先级很低

useTransition

  • useTransition 允许组件在切换到下一个界面之前等待内容加载,从而避免不必要的加载状态
  • 它还允许组件将速度较慢的数据获取更新推迟到随后渲染,以便能够立即渲染更重要的更新
  • useTransition hook 返回两个值的数组
    • startTransition 是一个接受回调的函数。我们用它来告诉 React 需要推迟的 state
    • isPending 是一个布尔值。这是 React 通知我们是否正在等待过渡的完成的方式
  • 如果某个 state 更新导致组件挂起,则该 state 更新应包装在 transition 中

SSR 水合

  • 之前的 SSR 请求是串行的 上一个请求没返回 下一个是不会返回的
  • 18 之后 就算上一个请求没有返回 下一个也可以直接返回

reactDOM.render

核心方法 createDOM

  • 根据传入的虚拟节点类型 来进行不同的节点操作
    • 文本类型
      • 创建文本
    • 元素节点类型
      • 创建元素节点
    • 函数类型
      • 看看有没 isReactComponent 这个变量 有的话就是类组件 没有的话就是函数组件
        • 执行函数得到真实的虚拟节点 重新执行 createDOM
        • 如果是类组件的话 new 一个类的实例 执行 willMount 生命周期 如果 ref 存在 就把实例挂载 ref.current 上 实例.render 执行 生成真正的虚拟 dom 重新执行 createDOM
    • 文档碎片类型
      • 创建一个文档碎片
    • ref 类型
      • ref 是 class 类的父级对象 ref 对象内的 type 指向的就是 class 实例 所以要 type.render 执行 得到真正的虚拟 dom
    • 元素类型
      • 创建一个真实 DOM 节点
    • provide 类型
      • 根据 type 的 context 生成 context
      • 让 context.currentValue 等于 props.value
      • 生成虚拟节点
      • 重新执行 createDOM
    • context 类型
      • 获取 provide 的 context
      • 用 content 的 currentValue 生成虚拟 dom
      • 重新执行 createDOM
    • memo 节点
      • 让 type 的 type 执行 生成一个虚拟节点
      • 重新执行 createDOM
  • 更新 props 属性
    • 合成事件
      • 给 dom 上创建一个唯一对象 store
      • 让 store 对象的 eventType 等于用户自定义的方法和事件
      • 如果 document 上没有这个 eventType 属性 则给 document 上挂载这个属性的合成事件 dispatchEvent
        • 获取事件名称和事件源
        • 定义事件方法
        • 开启记录批量更新
        • 生成合成事件 createSyntheticEvent
          • 循环 event 达到每一项 并改变 this
          • 把原生事件挂载到合成事件上
          • 默认阻止冒泡和默认事件
          • 重新 prevDefault 事件 兼容 ie
          • 重写 stopPropagation 事件 兼容 ie
        • 获取事件源 进行事件委托
        • 模拟事件冒泡
        • 结束批量更新操作
  • 如果该节点下存在子节点 则递归调度子节点
  • 把最终生成的真实 Dom 挂载到 root 上

JSX 渲染

  • createElement 方法的语法糖
  • 根据入参内容 如果大于 3 就要循环处理子节点
  • 将单独拿到的每个节点进行处理 如果是字符串或者数字 要转成对象 type 是 REACT_TEXT 统一代码结构 方便之后的 DIFF 移动
  • 生成虚拟节点(本质上还是一个对象 根据对象不同 key 对应的类型不同 来区分是什么节点)
return {
  $$typeof: REACT_ELEMENT,
  type,
  ref,
  key,
  props,
};

setState 更新

  • 首先是同步异步问题
  • 本质上是有个任务队列(链表) 调用 setState 的时候 会给这个队列里增加一个一项 批量更新 统一执行
  • 但是 setTimeout 这些事件不属于合成事件内的 所有就会同步执行
  • 解决方案 ustable_batchUpdates()方法包裹一层就可以了
    • 原理就是打开批量更新 把回调函数执行完了 再把批量更新关上 暴力解法
  • 深入原因的话
    • 之前的版本对比批量更新的判断只是用了一个变量 isBatchUpdate 来判断是否批量更新
    • react17 有了 lane 更新优先级 在判断批量更新的时候会根据优先级来判断 一共 31 的 lane
    • react18 有了并发模式 createRoot 代替 render 函数
      • 任务变的可中断 就不需要在通过 ustable_batchUpdates 来强制开启批量了
  • 每一项的 fiber 节点结构
    • 每个 fiber 节点都有个 updateQueue 代表更新队列 本质上是个链表
    • 父节点的 child 属性执行子节点 子节点的 return 属性指向父节点
    • 函数实例上的 reactInternals 指向父节点
  • 调用 setState 时
    • 调用 Updater 的 enqueueSetState 方法 把新状态入队
      • 通过 reactInternals 获取 fiber
      • 计算超时时间 避免低优先级的任务一直执行不了 超过这个事件 就把优先级调到当前事件执行优先级 1
      • 根据超时时间和事件优先级 +链表指针 构建一个新的 update 对象
      • 让 setState 传入的要更新的值挂载到 update 的 payload 上
      • 给实例的 fiber 的 updateQueue 队列中 Push 当前 update 对象
      • 开始调度
        • 找到根节点 markUpdateLaneFromFiberToRoot
        • 创建一个任务 从根节点开始进行更新 ensureRootIsScheduled
          • 判断当前根节点正在执行的任务优先级 是否等于最高优先级 进行 setState 拦截 把后面执行的直接 return
            • 是的话 就说明当前还在同一个 setState 里 直接 return
          • 把真正的渲染任务添加到一个 syncQueue 队列中 scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
            • performSyncWorkOnRoot 是真正的渲染任务 走 fiber 的调和提交流程
          • 在微任务队列 queueMicrotask 中 执行 syncQueue 中的所有渲染函数 flushSyncCallbackQueue 执行更新队列中的函数 清空队列
      • 如果不是并发模式 且非批量更新 直接调用更新函数
  • performSyncWorkOnRoot 给实例的 state 重新赋值
  • performSyncWorkOnRoot 更新视图
    • compareTwoVdom 进行 vdom 比较
      • 查找老节点的真实节点
      • 获取新节点的真实节点
      • 替换
        • 如果老节点和新节点都不存在
          • 直接 return
        • 老节点存在 新节点不存在
          • 删除老节点 并删除节点相关的 ref 和 children
        • 老节点不存在 但是新节点存在
          • 创建新节点 判断下一个节点是否存在 如果存在 就在下一个节点前插入 不存在 就插入在老节点之后
        • 老节点存在 新节点存在 并且 节点类型不同
          • 删除老节点 创建新节点 判断下一个节点是否存在 如果存在 就在下一个节点前插入 不存在 就插入在老节点之后
        • 老节点存在 新节点存在 并且节点类型相同
          • 对新老节点进行深度对比
          • 如果老节点是 memo 节点
            • 浅比较两个对象是否相等
              • 相等的话 不同更新
            • 不相等的话
              • 得到老节点
              • 从新节点里解构出 function 并执行
              • 拿到最新的渲染函数
              • 进行新旧节点对比 compareTwoVdom
          • 如果老节点是 context 节点
            • 获取老节点
            • 根据老节点拿到父节点
            • 获取新节点的 type 和 props
            • 从 type.context 得到 context 对象
            • props.children 执行得到最新的渲染函数
            • 进行新旧节点对比 compareTwoVdom
          • 如果老节点是 provide 节点
            • 获取老节点
            • 根据老节点拿到父节点
            • 获取新节点的 type 和 props
            • 让 type 的 context 和 currentValue 等于 props 的 value
            • 得到最新的渲染函数
            • 进行新旧节点对比 compareTwoVdom
          • 如果老节点是文本节点
            • 获取老节点赋值给新节点的 dom 属性
            • 老节点的 props 和新节点的 props 不全等
            • 让新节点的 textContent 等于新节点的 props
          • 如果老节点是文档碎片节点
            • 获取老节点的真实 DOM 挂载到虚拟节点的 dom 属性上
            • 更新子节点
          • 如果是函数或者类组件节点
            • 先判断 isReactComponent
            • 类组件
              • 让新虚拟节点的实例等于老虚拟节点实例
              • 更新类组件 使用 Update 类的 emitUpdate
            • 函数组件
              • 获取老节点
              • 老节点不存在直接 return
              • 解构出 type 和 props
              • 生成新的虚拟函数
              • 进行新旧节点对比 compareTwoVdom
          • 如果是元素节点
            • 重新定义(格式化)老的子节点列表
            • 重新定义(格式化)新的子节点列表
            • 构建一个 map 对象
            • 构建一个初始索引
            • 循环老节点
              • 如果每一项中存在 key 就用 Key 作为索引,没有 key 就用 index
              • 分别添加到 map 对象中
            • 创建一个 DOM 补丁包 来收集 DOM 操作 patch
            • 循环新的子节点列表
              • 记录当前索引
              • 如果每一项中存在 key 就用 Key 作为索引,没有 key 就用 index
              • 找到索引对应的老儿子
              • 判断老儿子是否存在
                • 重新更新元素
                • 如果老节点的索引小于最后的索引 给 patch 中追加对象 类型为移动
                • 老节点已经被复用,从 map 中删除
                • 将最后的索引标识改为索引标识或者老节点索引中最大的那个值
              • 不存在
                • 给 patch 中追加对象
            • 过滤出来补丁包中需要移动的节点
            • 先把要移动的和删除的都删除掉
            • dom 补丁包循环
              • 解构出 type 类型(移动或者插入)
              • 获取老的真实子节点集合
              • 如果类型是插入
                • 获取新的真实节点
                • 从老的子节点集合中找到当前子节点
                  • 存在就在之前插入
                  • 不存在就在之后插入
              • 如果类型是移动
                • 获取新的真实节点
                • 从老的子节点集合中找到当前子节点
                  • 存在就在之前插入
                  • 不存在就在之后插入

新的生命周期

  • getDerivedStateFromProps
    • getDerivedStateFromProps(props, state) 这个生命周期的功能实际上就是将传入的 props 映射到 state 上面
  • getSnapshotBeforeUpdate
    • getSnapshotBeforeUpdate() 被调用于 render 之后,可以读取但无法使用 DOM 的时候。它使您的组件可以在可能更改之前从 DOM 捕获一些信息(例如滚动位置)。此生命周期返回的任何值都将作为参数传递给 componentDidUpdate()

高阶组件

  • 高阶组件就是一个函数 传给它一个组件, 返回一个新的组件
  • 高阶组件的作用其实就是为了组件之间的代码复用
  • 属性代理 基于属性代理:操作组件的 props
  • 反向继承 基于反向继承:拦截生命周期、state、渲染过程