xdm,又要到饭了,又更新代码了!
总结一下上一篇完成的内容,
- 实现了mini fiber 的第3版,实现了fiber渲染真dom的过程。
有兴趣的可以点这里查看fiber 的迷你版实现 (3)
经过几版fiber的原理讲解,相信你对fiber至少有了一定的认识。有了对fiber的理解,现在就开始讲解fiber和hook的关联以及hook底层的原理。
还记得在最前面的几个章节讲解了 useState / useEffect 实现,比如 useState的实现,
//let state
let hooks = []
let currentHook = 0
function useState(initial) {
//state = state || initial
const hookIndex = currentHook
hooks[hookIndex] = hooks[hookIndex] || initial
const setState = newState=>{
// 这里会更新组件内部状态值,
//state = newState
hooks[hookIndex] = newState
// 也会更新ui
renderApp()
}
const state = hooks[hookIndex]
// 这里的index的改变控制了对应的多个usestate 的状态值
currentHook++
return [state, setState]
}
刚开始我们没有讲解fiber的实现,所以这个的实现没有fiber的参与。
组件ui的呈现其实就是一系列更新之后最后状态的呈现,用户会知道一个最后状态的值。那么真dom的更新会在commit阶段,这个前几章讲解fiber树已经解释了,所以hook的更新会体现在fiber上面。
我们复习一下在之前的fiber数据结构上面的2个属性, hooks, currentHook。 currentHook 是一个索引,由于组件可能存在多个hooks,所以currentHook 维护了当前属于哪个hook。hooks就是一个数组,搜集所有的hooks, 比如 useState / useEffect,这也需要了解可能存在多个相同的hook,比如一个组件有多个useEffect / useState。
class FiberNode {
constructor(type, props, parent = null, sibling = null, child = null) {
this.type = type; // 组件类型
this.props = props; // 组件属性
this.parent = parent; // 父节点
this.sibling = sibling; // 兄弟节点
this.child = child; // 子节点
this.effectTag = null; // 标记需要执行的操作(如更新)
this.state = null; // 组件的状态(useState)
this.hooks = []; // 组件的 Hook 列表
this.currentHook = 0; // 当前 Hook 的索引
this.alternate = null; // 交替的 Fiber
this.stateNode = null; // 真实 DOM 节点
}
}
接下来, 重构useState,
function useState(initial) {
// workInProgress , 当前fiber
const oldHook = workInProgress.alternate && workInProgress.alternate.hooks && workInProgress.alternate.hooks[workInProgress.currentHook]
const hook = {
state: oldHook.state || initial
queue: []
}
const actions = oldHook ? oldHook.queue : []
actions.forEach(action => {
hook.state = typeof action === 'function' ? action(hook.state) : action
})
if (oldHook)
oldHook.queue = []
const setState = action => {
if (typeof action === 'function') {
hook.queue.push(prevState=>action(prevState))
} else hook.queue.push(action)
requestHostCallback(()=>{
workLoop() // fiber树的更新
})
}
workInProgress.hooks.push(hook)
workInProgress.currentHook++
return [hook.state, setState]
}
- 首先尝试从当前的fiber树找出对应的fiber(当前的fiber树指的当前已经渲染的ui依赖的fiber树, react 还有另外一棵树,即 work in progress fiber树,即下一次ui更新依赖的fiber树)的获取值以及其所有的hooks(这里即是复用,尝试从当前ui渲染的fiber树找出需要的数据)
- 第一次进入,状态值即是initial (第一次运行workInProgress.alternate is null)
- 接着运行一遍所有当前fiber的hooks,确认状态值的一致性。(这里为什么需要重新运行一次所有的hooks呢?因为组件可能多次重新渲染(更新),useState 会重复运行,所以需要保持每次渲染的状态值的一致以及最新)
- 接着删除所有的hooks,重新收集
- 每当用户调用setState,正如你所知道的,setState 可以传入一个函数,第一个参数即是上次的状态值。 或者传入一个常数,即这个常数会覆盖上一次的状态值。这里进行了判断。接着把这个用户传入的新状态值(函数或者常数)压入队列
- 最后调用 requestHostCallback(workLoop) 进行fiber树的更新/构建,这个函数已经在前几篇里面实现了,
- 那么当第二次进入获取 oldHook,这个oldHook 就是上面6步完成的,即每次useState都缓存了本次的操作比如状态值以及hooks以便下次进行操作
- 最后更新currentHook的索引值
阅读了上面的实现,现在可以知道 setState 的更新属于异步。我们整理一下,
在 React 中,setState(或在函数组件中通过 useState 提供的 setState)的主要作用是更新组件的状态,并触发组件的重新渲染。在 Fiber 架构中,这一过程涉及以下几个步骤:
-
状态更新的调度:
- 当调用
setState时,状态更新被添加到一个队列中。 - 这些状态更新(动作)将被后续的渲染过程处理 (批量)。
- 当调用
-
Fiber 树的构建和协调(Reconciliation) :
- React 会构建一个新的 Fiber 树,表示更新后的组件结构。
- 新的 Fiber 树与旧的 Fiber 树进行比较,以确定哪些部分需要更新、插入或删除。
-
异步渲染和提交:
- React 使用调度机制(如
requestIdleCallback/Message channel或更高级的调度策略)来异步处理 Fiber 树的构建和协调。 - 一旦 Fiber 树的构建完成,React 会将变更提交到(commit 阶段)真实的 DOM 上,完成 UI 的更新。
- React 使用调度机制(如
这一章节讲解了更新了之前的useState函数实现,从源码角度的理解可以清楚的知道useState 属于异步更新。
但,这里的实现还没有完美,即现在每次的调用setState 都会重新渲染。这不是我们想要的,我们想要的多次更新merge只更新最新的那次数据,不是现在的每次都会单独更新一次。
下一篇将继续改进useState完成批量更新的相关实现
如果这样的长度/强度你觉得可以接受,觉得有帮助,可以继续阅读下一篇,实现一个 Mini React:核心功能详解 - 重构useState的实现 (2)。
如果文章对你有帮助,请点个赞支持一下!
啥也不是,散会。