东莞大岭山建网站公司,网站建设灬金手指下拉十五,杭州app开发制作公司,鞍山玉佛苑电话是多少前言
本文使用 React18.2.0 的源码#xff0c;如果想回退到某一版本执行git checkout tags/v18.2.0即可。如果打开源码发现js文件报ts类型错误请看本人另一篇文章#xff1a;VsCode查看React源码全是类型报错如何解决。
阅读源码的过程#xff1a; 下载源码 观察 package…前言
本文使用 React18.2.0 的源码如果想回退到某一版本执行git checkout tags/v18.2.0即可。如果打开源码发现js文件报ts类型错误请看本人另一篇文章VsCode查看React源码全是类型报错如何解决。
阅读源码的过程 下载源码 观察 package.json 使用的依赖以及构建相关的脚本 根据 核心API 寻找对应结构 packages/reactpackages/react-dompackages/react-reconcilerpackages/scheduler 串联整个流程 React项目初始化ReactDOM.renderReact18之前、ReactDOM.createRootReact18 数据更新是如何触发的this.setStatesetStateforupdate 基本API的使用方式 hooks、useState、useReducer、useId
一、ReactElement
React如何通过如下JSX代码生成DOM结构
const Element (div123/div
)借助 babel/plugin-transform-react-jsx-development 进行 Babel 编译JSX 代码段会变成标准的 React.createElement 调用形式。官方案例链接 React.createElement 的作用是创建 React元素JS对象。
观察源码可以发现 React 对于开发环境和生产环境的 createElement 做了不同处理。本文观察的React18.2.018.3.0对此进行了小改动 先观察生产环境下使用的createElementProd 根据传入的参数通过ReactElement()创建一个 React 元素 开发环境下的createElementWithValidation最终也是使用ReactElement()生成React元素 ReactElement工厂函数用于创建一个包含类型、属性、引用、唯一标识符等信息的 React 元素JS对象。 生产模式下只会创建一个简单的元素对象而在开发模式下会添加额外的调试信息和验证逻辑比如 key 和 ref 验证、来源追踪等。 使用时直接打印组件分别对应 查看typeof的标识 查看owner 发现ReactCurrentOwner.current类型为Fiber 二、Fiber
Fiber 是自 React 16 开始引入的一种新架构在此之前采用的 Stack Reconciler会同步地遍历整个组件树一旦开始渲染就会阻塞其他任务直到渲染完成。Fiber 可以将渲染工作拆分为更小的任务单元每个工作单元只渲染树中的一个节点并允许在任务之间进行中断和恢复从而改善了这一问题。
Fiber 使用双缓存机制来管理更新。current tree 代表当前页面显示的 Fiber 树work-in-progress tree 是当前渲染的新 Fiber 树当新的 Fiber 树完成时React 会将其替换为当前树。
1. Fiber工作流程
Fiber工作流程分为两个阶段分别为 Reconciliation 阶段调和阶段以及 Commit 阶段提交阶段
Reconciler 阶段「调和阶段」
该阶段会生成Fiber树得出需要更新的节点信息可以被中断去处理更高优先级的任务比如用户交互和动画。
这个阶段发生在虚拟 DOM即 Fiber Tree 中而不会直接影响实际的 DOM。Fiber Tree是链表结构使用diff算法将递归遍历变成循环遍历逐步对比每个节点的状态和属性构建出一棵新的 Fiber 树work-in-progress tree。然后配合requestIdleCallback API实现了任务的拆分、中断和恢复。
Commit 阶段「提交阶段」
一旦 work-in-progress 树构建完成并确定了需要执行的更新React 会进入 Commit 阶段将这些变更应用到真实DOM 中。
当所有的 DOM 更新完成后React 会将 work-in-progress tree 切换为 current tree即新的 Fiber 树变成当前页面上展示的树而之前的 current tree 会被丢弃。这种树的切换类似于双缓存的概念即始终有一棵树在页面上渲染而另一棵树则作为工作树进行更新。
该阶段会直接影响真实 DOM更新操作一旦开始无法被中断保证了 UI 的一致性和完整性。
三、Hooks
React 使用链表来管理函数组件中的 Hooks从而确保它们在每次渲染时按照固定的顺序执行和更新。如果强行改变 Hook 的执行顺序则会报错具体请看本人另一篇文章为什么Hooks不能出现在判断中。
下面先以使用频率最高的useState为主线剩余常用hook下文仍会讲述
1. resolveDispatcher
React 的 Hooks 系统通过 ReactDispatcher 来管理不同生命周期阶段的 Hook 调用。不同的渲染阶段如初次渲染、更新渲染会使用不同的 dispatcher 实现以便处理对应阶段的 Hooks 逻辑。
观察常用的Hook发现调用了 resolveDispatcher这是一个分发器主要用于在「函数组件」或自定义 Hook 中获取当前的 ReactDispatcher。 查看 resolveDispatcher 它取出并返回了ReactCurrentDispatcher.current。 继续查看ReactCurrentDispatcher.current它只是一个简单对象用于标记当前追踪的分发器。 Fiber中对ReactCurrentDispatcher.current进行了「初始化」以及「更新」的处理。 HooksDispatcherOnMount负责在组件初次挂载即组件首次渲染时处理 hooks 的调度工作。 HooksDispatcherOnUpdate 确保在组件更新阶段所有 hooks 能够按照正确的顺序和逻辑被执行并且能够访问和更新之前存储的状态。
下面分别观察二者
2. HooksDispatcherOnMount
查看最常用的useState 2.1 mountWordkInProgressHook()
其中 mountWordkInProgressHook() 用于在「函数组件」首次渲染时创建、初始化和链接 hooks 对象到链表中并更新指向当前工作中的 hook 节点的指针。保证了 React 在管理和调度 hooks 时能够按照正确的顺序操作每一个 hook并在后续的更新过程中正确地访问和更新这些 hooks 的状态。 2.1.1 mountWordkInProgressHook 链接Hook对象流程 创建 Hook 对象 memoizedState用于存储 hook 的状态值比如 useState 中的状态。baseState表示 hook 的初始状态。queue用于存储更新队列通常用在像 useState 这样的 hook 中。next指向下一个 hook 对象的引用形成链表结构。 链接 Hook 链表 「创建第一个 hook」 workInProgressHook 通常为 null会将 firstWorkInProgressHook 指向这个新创建的 hook 对象。 「后续的 hook」会将新创建的 hook 对象链接到当前链表的末尾workInProgressHook.next hook确保 hook 的执行顺序。 更新 Hook 指针: 在每次创建完新的 hook 对象后会更新 workInProgressHook 指针使其指向刚刚创建并链接的 hook。确保下一次 mountWorkInProgressHook() 时能正确地将新 hook 链接到链表的末尾。
2.2 queue
继续往下阅读代码这一部分是对setState函数方式赋值的处理。
const [count, setCount] useState(() 0)得到 initialState 后将其赋值给上一步 mountWordkInProgressHook() 创建的 hook对象 的 memoizedState 和 baseState。
然后创建 queue 状态更新队列其中
pending 存储当前挂起的更新链表当有新的状态更新时它们会被追加到这个链表中等待被处理。lanes 更新的优先级NoLanes 是默认值表示当前没有分配任何特定的优先级。dispatch 一个函数引用用于触发状态更新。调用 setState 或 dispatch实际上就是在触发 queue.dispatch这会触发一个新的状态更新流程。lastRenderedReducer 上一次渲染时使用的 reducer 函数reducer 函数用于计算新的状态basicStateReducer 是默认的 reducer 表示直接返回新的状态值。lastRenderedState 组件上一次渲染时的状态值用来确定是否需要触发重新渲染如果和本次一致则不会重新渲染。
扩展 queue.lanes 在 React18 之前是通过 expirationTime 实现的但是 React18 引入了新模型lanes它可以「中断更新」而且「排队」、「插队」也更优。
2.3 dispatchSetState()
继续阅读代码dispatch通过 dispatchSetState() 实现这个函数根据当前的渲染状态决定如何处理更新并在需要时触发组件的 「重新渲染」。
代码如下 参数
fiber: 当前组件对应的 Fiber 节点组件的状态和结构。queue: 状态更新队列 UpdateQueue存储了该组件的所有挂起的状态更新。action: 用户触发的状态更新动作可能是新的状态值或状态更新函数。
逐行分析 dispatchSetState()
const lane requestUpdateLane(fiber); 获取更新的优先级更新update状态更新的对象处理渲染
dispatchSetState()中还有一个很重要的函数requestEventTime()它用于在 React 调度事件时根据不同的上下文返回合适的时间戳。 继续查看 requestEventTime() 中 now() 的实现 通过代码可以发现 React 优先使用 performance.now() 提供高精度的时间戳用于调度和优化渲染过程对于不支持 performance.now() 的环境则使用Date.now()。二者的差别可以看本人另一篇文章Date.now()与performance.now()。
2.4 return
return就非常眼熟了返回的数组元素分别为状态以及修改状态这里有个小问题可以看本人另一篇文章useState为何返回数组而非对象 2.5 basicStateReducer
通过 mountState 和 mountReducer 可以证实 useState 是 useReducer 的 「语法糖」useReducer通过参数传递而useState通过basicStateReducer实现状态更新。 查看 basicStateReducer updateReducer中处理basicStateReducer 3. HooksDispatcherOnUpdate
更新 updateState中进行updateReducer 4. useCallback
经过上面的流程此时已经对useState工作机制了解了再来看看useCallback
4.1 挂载
同样是通过mountWorkInProgressHook() 创建、初始化和链接 hooks 对象到链表中并更新指向当前工作中的 hook 节点的指针判断依赖数组并更新hook状态。 4.2 更新
如果为hook已有状态更新渲染、提供了有效依赖数组、依赖数组与前一次状态一致则沿用上一次缓存的callback否则采用传入的。 is()用于比较两个值是否完全一致 即使 NaN 也会视为相等 5. useMemo
再来看看 useMemo不同于 useCallback 返回函数useMemo针对的是值其余逻辑一致。 6. useEffect
先来看挂载阶段的mountEffect 6.1 mountEffectImp 和 updateEffectImpl
mountEffectImpl 的任务就是挂载一个新的 useEffect并根据依赖数组确定副作用的触发条件。 updateEffectImpl 用于更新 useEffect 不论是mountEffectImpl还是updateEffectImpl最终都执行pushEffect下面继续查看updateEffectImpl。
6.1.1 pushEffect
pushEffect用于创建一个副作用对象并将它添加到 hook 的链表中。