useReducer返回state和dispatch,两个值,一个函数组件可以调用多个useReducer,不同的useReducer的state按顺序以链表的形式保存在该函数组件的fiber,memorizeState属性里。首次渲染会生成链表,更新时会从旧的fiber里按顺序拿到链表中对应的自己的值。
下面,让我们一步步来实现一下useReducer
1.先声明一个基本的架构
hook的数据结构是这样的
hook = {
memorizeState: null,
next: null
}
function updateWorkProgressHook() {}
export function useReducer(reducer, initialState) {
// 声明函数,获取当前fiber的hook
const hook = updateWorkProgressHook()
if (!currentlyRenderingFiber.alternate) {
// 通过是否有旧的fiber来判断是否是初次渲染
// 初次渲染hook的memorizestate 设置为初始值
hook.memorizeState = initialState
}
const dispatch = (action) => {
hook.memorizeState = reducer(hook.memorizeState, action)
// 更新该组件,更新组件的方法从外部引入,这里先不讨论
// 参数为当前正在渲染中的fiber
scheduleUpdateOnFiber(currentlyRenderingFiber)
}
return [hook.memorizeState, dispatch]
}
当dispatch执行时,先会改变该useReducer的hook的state,然后触发组件重新协调渲染。组件再次执行时改useReducer返回的state就是dispatch更新后的state值。
2.如何在useReducer执行前拿到当前函数组件的fiber对象
// 拿到当前节点
let currentlyRenderingFiber = null
// 拿到链表中当前hook
let workInProgressHook = null
// 该函数会在函数组件执行前触发,使函数组件执行时里面的useReducer能拿到currentlyRenderingFiber
export function renderHooks(wip) {
// 保存当前fiber
currentlyRenderingFiber = wip
//函数组件执行前改函数组件的fiber中的memorizedState值为null
currentlyRenderingFiber.memorizedState = null;
//函数组件执行前hook链表的当前节点为null
workInProgressHook = null
}
3.如何获取当前fiber的hook
// 首次渲染会生成新链表,更新时会从旧的fiber,memorizeState里按顺序拿到链表中对应的自己的值。
function updateWorkProgressHook() {
const current = currentlyRenderingFiber.alternate
let hook;
// 判断是否是新节点
if (current) {
// 有老节点,获得老节点对应的hook,返回hook
currentlyRenderingFiber.memorizeState = current.memorizeState
if (workInProgressHook) {
//是否头节点
hook = workInProgressHook = workInProgressHook.next
} else {
hook = workInProgressHook = current.memorizeState
}
} else {
hook = {
memorizeState: null,
next: null
}
// 新节点:建立hook链表
if (workInProgressHook) {
// 是否头节点
workInProgressHook = workInProgressHook.next = hook
} else {
workInProgressHook = currentlyRenderingFiber.memorizeState = hook
}
}
return hook
}