- 本文解析react实现hooks相关功能的源码,分析出主要思路和函数。
- 在hooks出来之前,函数组件内部不能持有状态,也不能触发副作用,但是hooks增强了函数组件,本文看看
useState是如何实现的。
function useState<S>(
initialState: S
): [S, Dispatch<S>] {
const dispather = resolveDispatcher();
return dispatcher.useState(initialState);
}
function resolveDispatcher() {
return ReactCurrentDispatcher.current;
}
function renderWithHooks(current: Fiber, Component: any) {
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
let children = Component(props);
ReactCurrentDispather.current = ContextOnlyDispatcher;
}
// 结束设置全局变量:ContextOnlyDispatcher
export const ContextOnlyDispatcher: Dispatcher = {
useCallback: throwInvalidHookError,
useContext: throwInvalidHookError,
useEffect: throwInvalidHookError,
useImperativeHandle: throwInvalidHookError,
useInsertionEffect: throwInvalidHookError,
useLayoutEffect: throwInvalidHookError,
useMemo: throwInvalidHookError,
useReducer: throwInvalidHookError,
useRef: throwInvalidHookError,
useState: throwInvalidHookError,
useDebugValue: throwInvalidHookError,
useDeferredValue: throwInvalidHookError,
useTransition: throwInvalidHookError,
useMutableSource: throwInvalidHookError,
useSyncExternalStore: throwInvalidHookError,
useId: throwInvalidHookError,
};
// 所有的hook函数都会抛出异常,提醒我们不能在函数组件外部使用hooks
function throwInvalidHookError() {
throw new Error(
);
}
// current为空时的赋值
const HooksDispatcherOnMount: Dispatcher = {
useCallback: mountCallback,
useContext: readContext,
useEffect: mountEffect,
useImperativeHandle: mountImperativeHandle,
useLayoutEffect: mountLayoutEffect,
useInsertionEffect: mountInsertionEffect,
useMemo: mountMemo,
useReducer: mountReducer,
useRef: mountRef,
useState: mountState,
};
// current不为空时的赋值
const HooksDispatcherOnUpdate: Dispatcher = {
useCallback: updateCallback,
useContext: readContext,
useEffect: updateEffect,
useImperativeHandle: updateImperativeHandle,
useInsertionEffect: updateInsertionEffect,
useLayoutEffect: updateLayoutEffect,
useMemo: updateMemo,
useReducer: updateReducer,
useRef: updateRef,
useState: updateState,
};
// 根据current是否为空,实际上运行hooks是不同的方法
// 例如useState:第一次运行的是mountState,第二次及以后运行的都是updateState
小结
- react在运行时动态修改全局变量,保证hook函数无法在函数组件外部执行
- 同一个hook函数在第一次和非第一次是调用不同的函数。如
useState分别调用mountState和updateState
mountState
function mountState(initState) {
const hook = mountWorkInProgressHook();
hook.memoizedState = hook.baseState = initState;
const queue = {
pending: null,
dispatch: null,
}
hook.queue = queue;
const dispatch = queue.dispatch = dispatchSetState.bind(
null,
currentlyRenderingFiber,
queue
)
return [hook.memoizedState, dispatch];
}
function dispatchSetState(fiber: Fiber, queue: UpdateQueue, action) {
const update = { action };
enqueueUpdate(fiber, queue, update);
schedueUpdateOnFiber(fiber)
}
function mountWorkInProgressHook() {
const hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null,
}
if (workInProgressHook === null) {
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
小结
mountState实际上就是创建hook对象
fiber.memoizedState指向第一个hook对象
- 调用hook函数n次,则创建n个hook对象,通过
next组成链表
updateState
// 当再次运行函数组件,调用到useState时,实际上调用的是updateState函数
// updateState函数实际上调用updateReducer函数
function updateState() {
return updateReducer(basicStateReducer)
}
function updateReducer(reducer) {
// 返回当前hook函数对应的hook对象
const workInProgressHook = updateWorkInProgressHook()
const queue = workInProgressHook.queue
const baseQueue = currentHook.baseQueue
const pendingQueue = queue.pending
// 将新的更新队列合并到尚未更新的队列中
if (pendingQueue !== null) {
// 当前hook还有遗留的更新(老的更新)
if (baseQueue !== null) {
// 把新的更新合并到老的更新中
const baseFirst = baseQueue.next
const pendingFirst = pendingQueue.next
baseQueue.next = pendingFirst
pendingQueue.next = baseFirst
}
// current.baseQueue上又是全部的更新,queue.pending就可以清空
currentHook.baseQueue = baseQueue = pendingQueue
queue.pending = null
}
// 如果有更新需要处理,则计算得到新的state
if (baseQueue !== null) {
const first = baseQueue.next
let update = first
let newState = currentHook.baseState
let newBaseQueueFirst = null
let newBaseQueueLast = null
do {
const updateLane = update.lane
if (!isSubsetOfLanes(renderLanes, updateLane)){
// 本次重新渲染不处理update对象,那么就copy一份
const clone = {
action: update.action,
next: null,
}
if (newBaseQueueLast !== null) {
newBaseQueueFirst = newBaseQueueLast = clone
newBaseState = newState
} else {
// 关联到尾巴上
newBaseQueueLast = newBaseQueueLast.next = clone
}
} else {
// 计算得到新的state
const acition = update.action
newState = reducer(newState, action)
}
update = update.next
} while(update !== null && update !== first)
if (newBaseQueueLLast === null) {
// 说明上面代码全部走else分支,即newBaseState还是null
// 所以这里赋值为最新的state
newBaseState = newState
} else {
// 说明newBaseQueueLast和newBaseQueueFirst都是有值的,使其成为一个环
newBaseQueueLast.next = newBaseQueueFirst
}
// 保存新的state
workInProgressHook.memoizedState = newState
// 保存新的baseState
workInProgressHook.baseState = newBaseState
// 保存没有处理的更新队列
workInProgressHook.baseQueue = newBaseQueueLast
}
const dispatch = queue.dispatch
// 最终还是返回memoizedState
return [workInProgressHook.memoizedState, dispatch]
}
// 作用找到正确的hook对象
function updateWorkInProgressHook() {
// 获取下一个currentHook对象
// currentHook表示当前函数组件在current树上对应的fiber节点上的hook对象
let nextCurrentHook
if (currentHook === null) {
// 没有currentHook,表明第一次调用,所以尝试取memoizedState
const current = currentlyRenderingFiber.alternate
if (current !== null) {
nextCurrentHook = current.memoizedState
} else {
// 这里是什么情况呢?
nextCurrentHook = null
}
} else {
// 直接找下一个
nextCurrentHook = currentHook.next
}
// 获取下一个workInProgressHook对象
// workInProgressHook表示workInProgress树的fiber对象的hook节点
let nextWorkInProgressHook
if (workInProgressHook === null) {
// workInProgressHook为空,说明查找的是第一个hook函数对应的hook对象
// 则从fiber对象上找
nextWorkInProgressHook = currentlyRenderingFiber.memoizedState
} else {
// next就是下一个hook对象
nextWorkInProgressHook = workInProgressHook.next
}
currentHook = nextCurrentHook
if (nextWorkInProgressHook !== null) {
// 找到了下一个hook对象,赋值到workInProgressHook
workInProgressHook = nextWorkInProgressHook
nextWorkInProgressHook = nextWorkInProgressHook.next
} else {
// 一般是当第二次fiber节点时。
// 因为第一次渲染构建workInProgress,对应的alternate是null
// 所以第二次构建workInProgress时,就可以复用第一次的hook对象
if (nextCurrentHook === null) {
// 说明workInProgress中的hook函数比上次多了,有问题
throw new Error()
}
const newHook = {
memoziedState: currentHook.memoziedState,
baseState: currentHook.baseState,
queue: currentHook.queue,
next: null
}
if (workInProgressHook === null) {
currentlyRenderingFiber.memoizedState = workInProgressHook = newHook
} else {
workInProgressHook = workInProgressHook.next = newHook
}
}
return workInProgressHook
}
小结
updateWorkInProgressHook的作用就是找到正确的hook对象
updateReducer首先合并新老更新队列,如果需要更新,则计算得到新的memoizedState
- TODO 在
updateReducer中,newBaseState只是赋值成第一次不用更新时刻的newState的值,如果后续newState继续变化的话,那么newBaseState是不会更新的,这样不会有问题吗?