我正在参与掘金创作者训练营第4期,点击了解活动详情,一起学习吧!
大家好!我是前端爬楼工程师,是一名热爱学习的野生程序员,希望结识更多朋友一起进步.
本篇讲的内容是函数组件的hooks,同步代码在这里4.0分支
1. hook的结构
首先我们看看hook是什么样的?
hook = {
memorizedState: null,
next: null
}
照代码上看,hook是一个链表结构, 函数组件使用多个hook的时候, 他们的结构应该是用next一直往后拼接的.
2. hook如何存储的
众所周知,react虚拟dom是fiber结构,hooks的状态最好记录的方式就是存储到fiber上.
我们在渲染函数组件的时候,同时调用了renderWithHook()方法来初始化hook, 初始化了以下几个变量
// 当前渲染的fiber
let currentRenderingFiber = null
// 当前处理的hook
let workInProgressHook = null
function renderWithHook(fiber){
currentRenderingFiber = fiber
currentRenderingFiber.memorizedState = null // 当前fiber状态
workInProgressHook = null
}
3. useReducer的实现
以useReducer为例,我们看一下react在初始化和更新的时候是如何创建和更新hook的.
首先,useReducer接收的参数是一个改变状态的reducer和一个初始状态, 返回的是最新的状态,修改状态的函数
接着,考虑fiber是初始化还是更新阶段:
- 初始化的时候,hook的状态直接取初始状态
- 更新阶段,hook的状态取reducer执行后的状态
最后,我们调用scheduleUpdateOnFiber(fiber)更新当前fiber,开启任务调度.
export function useReducer(reducer, initialState){
const hook = updateWorkInProgressHook()
// 如果fiber没有老节点 认为是初始化
if(!currentRenderingFiber.alternate){
hook.memorizedState = initialState
}
// 这里需要绑定作用域 在渲染函数组件的时候, hook和currentRenderingFiber做关联
const dispatch = dispatchReducerAction.bind(null, currentRenderingFiber, hook, reducer )
return [hook.memorizedState, dispatch]
}
function dispatchReducerAction(fiber, hook, reducer){
hook.memorizedState = reducer(hook.memorizedState)
fiber.alternate = {...fiber}
fiber.sibling = null // 这里只提交自己不需要提交兄弟 所以把兄弟置空
scheduleUpdateOnFiber(fiber) // 更新fiber
}
4. updateWorkInProgressHook的实现
updateWorkInProgressHook的主要工作是处理函数组件的所有hook,将他们组成链表结构.
组成链表的处理也同样需要考虑初始化和更新阶段:
- 初始化阶段:
- 考虑是否有当前工作的workInProgressHook, 如果没有创建头节点
hook = { memorizedState: null, next: null }- 然后将hook放到当前渲染的fiber上
workInProgressHook = currentRenderingFiber.memorizedState = hook- 接着就是next一个一个拼接
workInProgressHook = workInProgressHook.next = hook - 更新阶段: 更新阶段考虑的方式和初始化是一样的.
不过更新阶段hook的头节点是老节点状态
currentRenderingFiber.memorizedState接着就是取workInProgressHook.next的
// 当前渲染的fiber
let currentRenderingFiber = null
// 当前处理的hook
let workInProgressHook = null
function updateWorkInProgressHook(){
let hook;
const current = currentRenderingFiber.alternate
if(current){
// 更新阶段
currentlyRenderingFiber.memorizedState = current.memorizedState
if(workInProgressHook){
//workInProgressHook存在说明hook有头节点
workInProgressHook = hook = workInProgressHook.next
}else{
workInProgressHook = hook = currentRenderingFiber.memorizedState
}
}else{
// hook 0
hook = {
memorizedState: null,
next: null
}
// 初次渲染
if(workInProgressHook){
//workInProgressHook存在说明hook有头节点
workInProgressHook = workInProgressHook.next = hook
}else{
workInProgressHook = currentRenderingFiber.memorizedState = hook
}
}
}
5 其他处理
createFiber()方法里fiber需要增加一个属性alternate老节点
reconcilerChildren()需要协调老节点
function reconcilerChildren(wip, children) {
if (isStringOrNumber(children)) return
const newChildren = isArray(children) ? children : [children]
let previousNewFiber = null // 记录上一次的fiber
let oldFiber = wip.alternate?.child
for (let i = 0; i < newChildren.length; i++) {
const newChild = newChildren[i];
const newFiber = createFiber(newChild, wip)
const same = sameNode(newFiber, oldFiber)
if(same) {
Object.assign(newFiber, {
stateNode: oldFiber.stateNode,
alternate: oldFiber,
flags: Update
})
}
if(oldFiber){
oldFiber = oldFiber.sibling;
}
if (i == 0) {
wip.child = newFiber
} else {
previousNewFiber.sibling = newFiber
}
previousNewFiber = newFiber
}
}
function sameNode(a, b) {
return a && b && a.type == b.type && a.key == b.key
}
updateNode(node, preVal, nextVal)里需要对点击事件做一个简单的事件监听.
commitWorker()里需要增加对于flags 为 Update的 fiber 更新处理.
useState 已更新到 4.0 分支
下一篇: 我们看看min-react 删除更新的实现