React 设计原理 Part 3 实现篇

Posted by CodingWithAlice on December 22, 2024

React 设计原理 Part 3 实现篇

本文主要是对《React 设计原理》一书的笔记和总结,方便之后回顾只看笔记,不再重新细读书籍。

目录

状态更新流程 - 性能优化的两个策略

1.1、编写能够命中性能优化策略的组件

1.2、调用性能优化 API,命中 bailout 策略

hooks实现

看本章时还是带着问题阅读: 1、React 怎么做的性能优化?

注意:本章前面几小节在讲述不同事件的更新的不同优先级。没看。

只需要知道:更新对应数据结构 Update,它将参与计算 state

1、编写能够命中性能优化策略的组件

、调用性能优化 API,命中 bailout 策略

3、VDOM和Diff算法

 2、React hooks 的原理是什么?

性能优化

由于无法在编译时做出优化,所以在运行时做了几个优化策略

1.1 编写能够命中性能优化策略的组件

策略1:eagerState 策略
  • 策略结果:发生于触发状态更新时,命中该策略的更新不会进入 schedule 阶段,也不会进入 render 阶段
  • 策略逻辑:如果某个状态更新前后没有变化,则跳过后续更新流程
  • 策略前提条件:判断 fiberNode 是否存在“待执行的更新”,不存在时,尝试基于本次更新计算 eagerState(判断 current 和 wip是否同时满足 state 没有变化)
策略2:bailout 策略
  • 策略结果:在 render 阶段,命中该策略的组件的子组件会跳过 reconcile 流程 -> 即子组件不会进入 render 流程

  • 策略逻辑:表示子 fiberNode 没有变化(state、props、context),可以复用

  • 一个 beginWork 流程里面有两次命中策略的机会

    464CEDA5-F606-414E-A1E6-FC1A7DFDFBDF

    • ①第一次命中策略条件(需要同时满足4个条件):
      • 条件1:props 全等比较
        • props 的组件比较是 render 返回的 JSX(createElement 的语法糖)- 实际上就是一个包含 props 属性的对象
        • 即使本次和上次更新的 props 的每一项都没有变化,但是本次是 createElement 结果的一个全新的 props 引用,并不全等
        • 只有当父 fiberNode 命中 bailout 策略,复用子 fiberNode,新旧 props 才会全等
          • 特殊情况:当前 fiberNode 是 HostRootFiber 时,其挂载的组件不变时,它的子组件将被复用,也就是 props 始终满足全等条件
      • 条件2:Legacy Context
        • 为了更好得实现 bailout 策略,重构过
        • 改造后:如果子树深处存在 context Consumer,即使子树的根 fiberNode 命中了 bailout 策略,也不会完全跳过子树的 beginWork 流程
      • 条件3:fiberNode.type 没有变化
        • 由于 App 每次 render 后都会创建新的 Child 引用,对子组件来说,type 始终是变化的,无法命中 bailout 策略
      • 条件4:当前 fiberNode 没有更新发生(计算得到是否存在 state 变化)
        • 整棵子树都命中 bailout 策略 - 通过 wip.childLanes
          • 命中后,跳过整棵子树的 render,复用 current child 克隆出 wip child
        • 只有子 fiberNode 命中 bailout 策略
    • ②第二次命中策略条件(满足任一条件)
      • 可能性1:开发者使用了性能优化 API
      • 可能性2:虽然有更新,但 state 没有变化(经过 update 计算)

1.2 调用性能优化 API,命中 bailout 策略

工作原理:改写 bailout 策略中的条件1,即 props 的全等比较,太难满足,改为浅比较

  • shouldComponentUpdate
  • PureComponent
  • React.memo
  • useMemo、useCallback

日常开发中,即使不使用性能优化 API,只要满足一定条件,也能命中 bailout 策略

  • 将可变部分与不可变部分分离,没有 state 变化的部分可以命中 bailout 策略
  • 注意根节点如果不是 HostRootFiber,此时需要使用性能优化 API

Hooks实现

Hooks 的执行逻辑大体一致:

  • Step1: FC 进入 render 流程前,通过判断 current !== null && current.memoizedState !== null,确定当前Hooks 执行的上下文环境
    • 其中 fiberNode.memoizedState 存储的是 FC 对应的 hooks 链表,即链表中的第一个 hooks
    • hook.memoizedState 代表了某个 hook 自身的数据
  • Step2:进入 mount/update 流程时,执行 mount/update 对应逻辑
  • Step3:其他情况 hook 执行,依据当前上下文处理
useEffect

​ useEffect 的回调函数会在 commit 阶段完成后异步执行,所以不会阻塞视图渲染

useMemo 和 useCallback 区别
  • useMemo 用于缓存一个值(会执行传入函数,并返回需要缓存的值)
  • useCallback 用户缓存一个函数
useRef

useRef 任何需要被引用的数据都可以被保存在 ref 中

  • 工作流程分为两个阶段:
    • render 阶段:标记 Ref flag
      • mount时 ref props 存在 / update 时 ref props 变化 -> 通过 markRef 在 wip.flags 中标记
    • commit 阶段:根据 Ref flag 执行 ref 相关操作
      • 在Mutation 阶段移除旧的ref currentR.current(置为 null)
      • 在 Layout 阶段 重新赋值