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 流程里面有两次命中策略的机会
- ①第一次命中策略条件(需要同时满足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 策略
- 整棵子树都命中 bailout 策略 - 通过 wip.childLanes
- 条件1:props 全等比较
- ②第二次命中策略条件(满足任一条件)
- 可能性1:开发者使用了性能优化 API
- 可能性2:虽然有更新,但 state 没有变化(经过 update 计算)
- ①第一次命中策略条件(需要同时满足4个条件):
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 阶段 重新赋值
- render 阶段:标记 Ref flag