react源码debugger-各个hooks的逻辑实现(useState和useEffect)
Posted coderlin_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了react源码debugger-各个hooks的逻辑实现(useState和useEffect)相关的知识,希望对你有一定的参考价值。
了解react的整体流程,会有助于理解本文。
hooks是什么?
要了解hooks是什么,我们得先想知道react怎么执行函数组件。
先看看函数组件的fiber是什么?
const fiber =
type: f App(), //函数本身,
memoziedState: , //hooks链表
updateQueue: , //effects链表
....
对于函数组件,我们现在只需要关注他这几个属性就行了
首先看到renderWithHooks函数,他是执行函数组件的方法。
function renderWithHooks(current, workInProgress, Component, props,secondArg, nextRenderLanes)
// 将当前fiber赋值给currentlyRenderingFiber,hooks执行的时候通过这个获取当前的fiber对象
currentlyRenderingFiber = workInProgress;
// 清除fiber上面的memoizedState和updateQueue,为啥呢?因为即将执行函数组件,
// 函数组件的hooks对象以链表形式存放在fiber.memoizedState
// 函数组件的useLayoutEffect和useEffect以effects链表的形式存放在fiber.updateQueue上
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
// 赋值hooks对象
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
// 真正执行函数
let children = Component(props, secondArg);
// 重新赋值hooks对象。此时不能再调用hooks,会报错
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
// 重新置空全局变量
currentlyRenderingFiber = (null: any); //指向当前的函数fiber
currentHook = null; //current树上的指向的当前调度的 hooks节点。
workInProgressHook = null; //workInProgress树上指向的当前调度的 hooks节点。
return children
如上,可以看出,renderWithHooks主要做了:
- 将当前调度的函数fiber赋值currentlyRenderingFiber,从名字可以看出,currentlyRenderingFiber就是当前调度的fiber,hooks执行的时候,通过全局变量currentlyRenderingFiber获取到当前的fiber。
- 清除fiber上面一些属性,如fiber.memoizedState和fiber.updateQueue。fiber.memoizedState存放着hooks链表,指向第一个hooks。而updateQueue存放着useEffect/useLayoutEffect产生的effects。为什么这里要清除呢?因为函数组件即将执行,每一次重新执行函数组件,都会重新生成effects链表和hooks链表。
- 赋值ReactCurrentDispatcher.current属性,我们调用的hooks实际上就是通过ReactCurrentDispatcher.current获取到的,在一开始是null。所以在函数组件外部执行的时候会报
null上没有useState
等的错误。可以看到执行函数组件之后,又赋值了ContextOnlyDispatcher,这个其实就是用来抛出错误的,因为函数组件已经执行完毕了,不能再执行hooks了。 - 执行函数组件,可能会执行各种hooks。
- 执行完函数组件之后,需要重新赋值全局变量,比如currentlyRenderingFiber置为null。因为要轮到下一个函数fiber调度了。curretnHook置为null。这个全局变量其实就是用来指向current fiber上的hooks节点,通过currentHook,复制出一个新的workInprogress hook。workInporgressHook跟currentHook一样,不过它指向的是wokrInporgress hook。
- 返回children
现在来看这个demo。
const App2 = () =>
const [state, setState] = useState(1);
const stateMemo = useMemo(() =>
state + 1;
, [state]);
const stateUseCallback = useCallback(() =>
return state + 1;
, [state]);
const stateRef = useRef(state);
useEffect(
function useEffect()
console.log("useEffect=====create");
return () =>
console.log("effect====destory");
;
,
[state]
);
useLayoutEffect(
function useLayoutEffect()
console.log("useLayoutEffect=====create");
return () =>
console.log("useLayoutEffect====destory");
;
,
[state]
);
return (
<div onClick=() => setState(3)>
<div>state: state</div>
<div><>memo: stateMemo</></div>
<div>callback: stateUseCallback()</div>
</div>
);
;
预知识:
每一个hooks都会创建一个hooks对象。结构基本一样
const Hook =
queue: , //挂载着更新的操作,类似于类fiber.updateQueue
memoziedState: , //保存hook的状态,不同的hook保存的数据不同
baseState: , // 如果有跳过的update,存放的就不是最新的state,而且跳过的update之前的state
baseQuuee: , //存放着因为优先级较低而跳过的hook
next: ,//指针,指向下一个hook
上面我们说过,fiber.memoizedState存放着hooks链表。
从demo debugger上,发现函数fiber是这样的
memoziedState:
baseQueue: null
baseState: 1
memoizedState: 1,
queue: ,
next:
baseQueue: null
baseState: null
memoizedState: (2) [2, Array(1)],
queue: null,
next:
baseQueue: null
baseState: null
memoizedState: current: 1
next: memoizedState: …, baseState: null, baseQueue: null, queue: null, next: …
queue: null
可以看到fiber.memozedState就存放着hooks链表,每一个hook通过next指针相连。
useState
useState的第一次执行的函数就是mountState
function mountState(initialState)
// 创建hooks对象
const hook = mountWorkInProgressHook();
//初始值是函数就执行
if (typeof initialState === 'function')
initialState = initialState();
// initState存放到hook对象上的memoizedState和baseState
hook.memoizedState = hook.baseState = initialState;
// 创建queue对象,每个hooks都有一个queue对象,用来存放当前hooks产生的update等信息。
const queue: UpdateQueue<S, BasicStateAction<S>> =
pending: null, //存放update链表
interleaved: null,
lanes: NoLanes, //优先级
dispatch: null,
lastRenderedReducer: basicStateReducer, //存放reducer,useState是指定的,而useReducer是自定义的,所以说 useState是特殊的useReducer
// 上一次render时候的update
lastRenderedState: (initialState: any),
;
hook.queue = queue;
// 派发action的函数,通过bind传入了当前的fiber,和Queue对象
const dispatch: Dispatch = queue.dispatch = dispatchSetState.bind(
null,
currentlyRenderingFiber, //函数的fiber
queue,
);
// 返回初始化状态和dispatch
return [hook.memoizedState, dispatch];
可以看到mountState做了
- 调用mountWorkInProgressHook创建hooks对象。
- 如果初始值是函数,就执行。
- 更新hooks属性
- 创建queue对象。
- 创建dispatch函数,就是他来派发action。
先看看mountWorkInProgressHook
function mountWorkInProgressHook(): Hook
const hook: Hook =
memoizedState: null, // useState中 保存 state 信息 | useEffect 中 保存着 effect 对象 | useMemo 中 保存的是缓存的值和 deps | useRef 中保存的是 ref 对象。
baseState: null, //usestate和useReducer中,一次更新中 ,产生的最新state值。
baseQueue: null, //usestate和useReducer中 保存最新的更新队列,存放着因为优先级比较低跳过的update链表
queue: null, //存放着本次更新相关的信息。
next: null, //指针
;
// 函数组件第一个hooks的时候,为空,通过workInProgressHook指针连接起整个函数组件的hooks链表。
if (workInProgressHook === null)
// hooks链表第一个,挂载在fiber.memoizedState
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
else
// 插在上一个hook对象后面,形成链表
workInProgressHook = workInProgressHook.next = hook;
return workInProgressHook;
可以看到,它创建了hooks,并且将hooks挂载到了fiber.memoizedState上,以链表的形式。workInProgressHook就是用来执行当前的hook。
queue对象
这个对象存放着更新的一系列值。
const queue: UpdateQueue<S, BasicStateAction<S>> =
pending: null, //存放update链表
lanes: NoLanes, //优先级
dispatch: null, //存放着setState
lastRenderedReducer: basicStateReducer, //存放reducer,useState是指定的,而useReducer是自定义的,所以说useState是特殊的useReducer
// 上一次render时候的update
lastRenderedState: initialState, //用来给setState第一次调用的时候做优化。
;
我们现在知道useState初始化会创建hooks,初始化hook.queue,挂载到fiber.memoizedState上,然后返回hook.memoizedState和dispatch。
更新的时候
更新调用的是dispatch。从mountState可以看到。
const dispatch: Dispatch = queue.dispatch = dispatchSetState.bind(
null,
currentlyRenderingFiber, //函数的fiber
queue,
);
调用dispatchSetState,并且传入了fiber和queue。
function dispatchSetState( fiber, queue, action
const lane = requestUpdateLane(fiber);
// 创建update对象
const update: Update<S, A> =
lane, //优先级
action, //值,对于useReducer来说是一个action
hasEagerState: false,
eagerState: null,
next: (null: any), //指针
;
// 插入当前update
enqueueUpdate(fiber, queue, update, lane);
// 获取workInprogress fiber
const alternate = fiber.alternate;
if( fiber.lanes === NoLanes &&
(alternate === null || alternate.lanes === NoLanes))
// 让如果当前的fiber没有处于调度状态,那么第一次调用setState就会走优化逻辑。
const lastRenderedReducer = queue.lastRenderedReducer; //获取reducer
const currentState: S = (queue.lastRenderedState: any); // 当前的state
const eagerState = lastRenderedReducer(currentState, action); //获取最新的值
update.hasEagerState = true; //打标机,表示这个update已经计算过值了。
update.eagerState = eagerState;
if (is(eagerState, currentState))
// 如果state没变,不调度
return;
const eventTime = requestEventTime(); //获取当前事件的执行时间
// 开启新的一轮调度。
const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
可以看到dispatchSetState主要做了
- 创建update,每一个更新都会创建对应的update对象。
- 调用enqueueUpdate将update插入hook.queue.pending上,以链表的形式。
- 判断是否处于调度状态,如果不是,当前的setState就可以直接计算值了。然后判断计算出来的值跟之前的state是否相等,相等则不调度,这也是函数的setState跟来组件的this.setState的区别。这里也不用担心说,万一计算出的值变了,那不还得继续调度,在这里如果计算值之后,会将update.hasEagerState置为true,表示已经计算过了,那么等调度完处理update的时候,就不会重新计算,而且直接取update.eagerState最后更新后的值。
- 调用scheduleUpdateOnFiber开启新的一轮调度。
总结:现在知道了setState会创建update,插入到hook.queue.pending上。然后调用scheduleUpdateOnFiber开启新的一轮调度。
接着我们看update阶段执行的useState,执行的是updateState函数。
// useState的update函数
function updateState<S>(
initialState: (() => S) | S,
):
// 跟useReducer调用同样的函数,不过第一个
return updateReducer(basicStateReducer, (initialState: any));
这个updateState,其实也是调用updateReducer,从名字上可以看到,就是useReducer的更新函数,但是它默认传了basicStateReducer
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S
// $FlowFixMe: Flow doesn't like mixed types
return typeof action === 'function' ? action(state) : action;
这个就是一个处理,如果setState传入的是普通值,那么reducer就直接返回,如果传入的是函数,那么就将当前的state传入,将返回值作为返回。这里也解释了为什么setState可以获取最新的State。我们来看updateReducer怎么计算新的state的。
function(reducer, initialArg)
// 通过current fiber的Hooks对象,一个一个复制对应的hooks返回
const hook = updateWorkInProgressHook();
// 获取更新队列
const queue = hook.queue;
queue.lastRenderedReducer = reducer; //赋值redcuer,每一次useReducer执行,都会重新赋值reducer
// ----------- 拼接跳过的update-----------
const current: Hook = (currentHook: any); //获取当前hooks的对应在current fiber上的hook
let baseQueue = current.baseQueue; //获取因为优先级较低而跳过的update链表
const pendingQueue = queue.pending; //获取当前的update任务链表
if (pendingQueue !== null)
if (baseQueue !== null)
const baseFirst = baseQueue.next;
const pendingFirst = pendingQueue.next;
baseQueue.next = pendingFirst;
pendingQueue.next = baseFirst;
current.baseQueue = baseQueue = pendingQueue;
queue.pending = null;
// -----------处理update链表--------------
// 如果有跳过的Update,他们存放在了hooks.baseQueue上面,将他们取下来,然后插入到当前的update链表之前
//存在更新的链表。
if (baseQueue !== null)
// ----- 定义一些变量存储值---------
// We have a queue to process.
const first = baseQueue.next; //获取第一额update
let newState = current.baseState; // 保存每一个update处理后得到的最新的state
let newBaseState = null;
//保存着这次调度之后,fiber的最新state,跟newState不一样的是,有些update因为优先级较低被跳过,所以newBaseState的值是停留在被跳过的update的state,为的就是保证状态不丢失
let newBaseQueueFirst = null; //新的需要跳过的update链表的第一个指针
let newBaseQueueLast = null; //新的需要跳过的update链表的最后一个指针
let update = first;
// do-while循环处理update
do
// -----------优先级判断-----------------
const updateLane = update.lane; //优先级
if(!isSubsetOfLanes(renderLanes, updateLane))
//如果当前的update优先级不够
//clone一个Update
const clone: Update<S, A> =
lane: updateLane,
action: update.action,
hasEagerState: update.hasEagerState,
eagerState: update.eagerState,
next: (null: any),
;
// --- 将跳过的update以链表的形式放到newBaseQueueFirst------
if (newBaseQueueLast === null) //这是第一个跳过的update
newBaseQueueFirst = newBaseQueueLast = clone;
newBaseState = newState;
else
// 不是第一个跳过的,直接插在链表后面
newBaseQueueLast = newBaseQueueLast.next = clone;
// 更新队列中的剩余优先级。
currentlyRenderingFiber.lanes = mergeLanes(
currentlyRenderingFiber.lanes,
updateLane,
);
else
//如果这个update优先级够了
//-----为了保持状态的连续性,若update之前有别的update被跳过了,那么当前的update也得clone一份存入跳过的链表
if (newBaseQueueLast !== null)
const clone: Update<S, A> =
lane: NoLane,
action: update.action,
hasEagerState: update.hasEagerState,
eagerState: update.eagerState,
next: (null: any),
;
newBaseQueueLast = newBaseQueueLast.next = clone;
// 开始处理这个update了,这里对照着上面setState做的优化,如果值已经计算了,直接取update.eagerState,
if (update.hasEagerState)
newState = ((update.eagerState: any): S);
else
const action = update.action;
// 通过reducer计算最新的state,赋值给newState
// 这里每一次调用reducer,都会将最新计算得到的NewState传入进去,所以这也是为什么,setState就可以获取到最新的state的原因了。因为上一个update处理过的state,已经赋值给了newState了。
newState = reducer(newState, action);
// 处理下一个update
update = update.next;
while (update !== null && update !== first); //直到所有的Update处理完毕
//----- 处理新的state了,如果有跳过的update,那么hook.memozedState就不能是最新的state。而是跳过的第一个update的时候的state
if (newBaseQueueLast === null)
//如果没有跳过的update,当前hooks.baseState才是最新的state
newBaseState = newState;
else
// 跳过的update链表首尾相连
newBaseQueueLast.next = (newBaseQueueFirst: any);
// 将状态更新到hook对象上
hook.memoizedState = newState; //存放最新的state
hook.baseState = newBaseState; //如果有跳过的update,存放的就不是最新的state,而且跳过的update之前的state
hook.baseQueue = newBaseQueueLast; //baseQueue存在跳过的update链表
queue.lastRenderedState = newState;
const dispatch: Dispatch<A> = (queue.dispatch: any);
return [hook.memoizedState, dispatch]; //这里返回的状态并不是newState,而是newBaseState
如上,可以看到useState的update执行的函数做的事情还是很多的。
-
调用updateWorkInProgressHook获取到hooks对象
-
判断是否有跳过的update链表,有的话就跟现在的update链表拼接到一起处理。
-
递归处理update。如果优先级不够的,就先放着,优先级够的,还得判断他前面有没有跳过的update,有的话那么当前的update也得clone一份放入跳过的链表之中。然后处理update
-
处理的时候会判断当前update是否已经计算过了,不是的话,就会调用reducer将hook.baseState传入。注意,这里处理update链表的基础state是hook.baseState。而不是hook.memoizedState,这是因为,为了状态的连续性,不止优先级较低的update,后面的update都得存起来,而且计算update的state必须留在跳过的update的时候的hook.state才行。这里通过reducer函数处理action,也可以知道为什么setState传入函数就可以获取最新的state,因为上一个Update已经计算得到新的值,然后才传入reducer的。
-
接着更新hook,可以看到,如果有跳过的update,那么hook.baseState赋值的是newBaseState。而且只当有没调过的update的时候,才会执行newBaseState=newState
-
最后返回最新的到的hook.memoizedState。
这里我们需要注意的有两点,一个是updateWorkInProgressHook如何获取hooks。一个是优先级跳过update,react为了避免状态丢失做的处理。
updateWorkInProgressHook
function updateWorkInProgressHook()
let nextCurrentHook: null | Hook;
if (currentHook === null)
// 函数组件第一个hooks,为null。
// 获取current fiber
const current = currentlyRenderingFiber.alternate;
if (current !== null)
// 从curentFiber上面获取mount时候创建的hook对象,现在nextCurrentHooks已经有一条完整的当前current fiber的hooks链表了
nextCurrentHook = current.memoizedState;
else
nextCurrentHook = null;
else
// 第二次,直接从current fiber上往下取第二个hooks
nextCurrentHook = currentHook.next;
let nextWorkInProgressHook: null | Hook; //获取workInprogress fiber下一个hook
if (workInProgressHook === null)
// 函数组件第一个hooks执行的时候,为null
//update时候, 获取workInprogress fiber上面第一个hooks对象,应该为null
nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
else
// 直接往workInprogress fiber的next下获取
nextWorkInProgressHook = workInProgressHook.next; //null
if (nextWorkInProgressHook !== null)
// 已经有值了,将nextWorkInProgressHook赋值给workInProgressHook去返回,
// There's already a work-in-progress. Reuse it.
workInProgressHook = nextWorkInProgressHook;
nextWorkInProgressHook = workInProgressHook.next;
currentHook = nextCurrentHook;
else
// Clone from the current hook.
// update 第一次进来应该为null
// 第一次进来nextCurrentHooks不应该为null
if (nextCurrentHook === null)
throw new Error('Rendered more hooks than during the previous render.');
// 指向current fiber对应的hooks对象
currentHook = nextCurrentHook;
// 从Curent fiber上面复制一个hooks给workInprogress fiber
const newHook: Hook =
memoizedState: currentHook.memoizedState,
baseState: currentHook.baseState,
baseQueue: currentHook.baseQueue,
queue: currentHook.queue,
next: null,
;
// update第一次为null
if (workInProgressHook === null)
// This is the first hook in the list.
// 将新创建的Hooks复制给wokrInprogress fiber的memoizedState,
currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
else
// Append to the end of the list.
workInP以上是关于react源码debugger-各个hooks的逻辑实现(useState和useEffect)的主要内容,如果未能解决你的问题,请参考以下文章
react源码debugger-各个hooks的逻辑实现(useState和useEffect)
react源码debugger-17,18版本的事件更新流程
react源码debugger-17,18版本的事件更新流程