源码结构
在渲染函数组件的时候会调用renderWithHooks
方法:
export function renderWithHooks<Props, SecondArg>(
current: Fiber | null,
workInProgress: Fiber,
Component: (p: Props, arg: SecondArg) => any,
props: Props,
secondArg: SecondArg,
nextRenderLanes: Lanes,
): any {
// 1
renderLanes = nextRenderLanes;
currentlyRenderingFiber = workInProgress;
...
// 2
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
workInProgress.lanes = NoLanes;
...
// 3
// 根据当前workInprogress树上的Fiber在current树上是否有对应的Fiber节点
// 来判断是创建hook,还是更新hook
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
// 4
// 调用函数组件
let children = Component(props, secondArg);
...
renderLanes = NoLanes;
currentlyRenderingFiber = (null: any);
// 5
// 重置hook链表指针
currentHook = null;
workInProgressHook = null;
...
return children;
}
可以看到这个函数中:
- 将
nextRenderLanes(当前需要渲染的优先级集合)
赋值给了renderLanes
,将workInProgress(函数组件对应的Fiber对象)
赋值给了currentlyRenderingFiber
,renderLanes
和currentlyRenderingFiber
是全局变量,在后面调用hook函数时使用。 - 将
workInProgress
的三个属性重置了:memoizedState,updateQueue,NoLanes
- 根据当前节点在current树上是否有相对应的节点判断是创建还是更新,以此来给
ReactCurrentDispatcher.current
赋值:
const HooksDispatcherOnMount: Dispatcher = {
...
useCallback: mountCallback,
useContext: readContext,
useEffect: mountEffect,
useImperativeHandle: mountImperativeHandle,
useLayoutEffect: mountLayoutEffect,
useMemo: mountMemo,
useReducer: mountReducer,
useRef: mountRef,
useState: mountState,
...
};
const HooksDispatcherOnUpdate: Dispatcher = {
...
useCallback: updateCallback,
useContext: readContext,
useEffect: updateEffect,
useImperativeHandle: updateImperativeHandle,
useLayoutEffect: updateLayoutEffect,
useMemo: updateMemo,
useReducer: updateReducer,
useRef: updateRef,
useState: updateState,
...
};
创建阶段赋值为HooksDispatcherOnMount
,更新阶段赋值为HooksDispatcherOnUpdate
,可以看到这两个对象中保存的都是hook函数,key都是一样的,只是在不同阶段所调用的hook函数是不一样的。
- 调用函数组件,此时会调用hook函数,返回React element对象
- 重置hook链表指针:
currentHook,workInProgressHook
,关于这两个指针十分重要,下面会详细分析。
组件挂载
下面我们则详细分析第4部分的代码:调用函数组件。
例如,我们现在有这样一个函数组件:
export default function Demo () {
const [num, setNum] = useState('value1');
const [num1, setNum1] = useState('value2');
const onTapBtn = () => {
setNum(num + 1);
}
return (
<button onClick={onTapBtn} >Demo:{num}</button>
);
}
调用函数组件后,首先会执行useState('value1')
,那么我们来看下useState
的源码:
export function useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
function resolveDispatcher() {
const dispatcher = ReactCurrentDispatcher.current;
...
return ((dispatcher: any): Dispatcher);
}
可以看到useState
中首先会获取dispatcher
,也就是我们上面所提到的第3部分的代码,将保存了hook函数的对象赋值给了ReactCurrentDispatcher.current
,在useState
函数中调用了resolveDispatcher
方法获取了ReactCurrentDispatcher.current
,随后调用了ReactCurrentDispatcher.current
中的useState
方法并传入了初始化的state
。
在dispatcher.useState
的函数内部,又调用了mountState
函数:
function mountState(initialState) {
const hook = mountWorkInProgressHook();
...
}
在mountState
中调用了mountWorkInProgressHook
函数,我们来看一下mountWorkInProgressHook
函数做了什么:
function mountWorkInProgressHook(): Hook {
const hook: 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;
}
首先创建了一个hook对象,然后判断workInProgressHook
是否为null,因为每次调用完函数组件,都会把workInProgressHook
置为null,也就是我们刚开始分析的源码结构的第5部分。此时又是第一次调用hook函数,workInProgressHook
肯定为null,所以会将新建的hook对象赋值给workInProgressHook
,然后将workInProgressHook
挂载到currentlyRenderingFiber(源码结构的第1部分:函数组件对应的Fiber对象)
的memoizedState
属性上。
最后返回workInProgressHook
,记住此时workInProgressHook
指向的是useState('value1')
所创建的hook对象。
mountWorkInProgressHook
函数执行完成后,继续向下执行:
function mountState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
const hook = mountWorkInProgressHook();
if (typeof initialState === 'function') {
initialState = initialState();
}
hook.memoizedState = hook.baseState = initialState;
const queue = (hook.queue = {
pending: null, // 需要更新的state
interleaved: null,
lanes: NoLanes, // 更新的lane优先级
dispatch: null, // 发起更新state的函数
lastRenderedReducer: basicStateReducer, // 上一次计算state的函数
lastRenderedState: (initialState: any), // 上一次更新的state
});
const dispatch: Dispatch<
BasicStateAction<S>,
> = (queue.dispatch = (dispatchAction.bind(
null,
currentlyRenderingFiber,
queue,
): any));
return [hook.memoizedState, dispatch];
}
先判断了initialState
,也就是我们使用useState时所传入的参数,如果是函数类型的,则会调用,获取到返回值。然后将值赋值给了hook对象上的baseState
和memoizedState
属性。
然后创建了一个队列对象,接着将dispatchAction
函数通过bind
方法传入函数组件对应的fiber
对象和队列对象,挂载到队列对象的dispatch
属性上。
最后返回一个集合,第一个元素是state的值,第二个元素是更新state函数(dispatchAction
)。
此时useState('value1')
调用完毕。
接着调用useState('value2')
,流程也是与useState('value1')
是一样的,不同点在于workInProgressHook
的指向,我们来看一下:
function mountWorkInProgressHook(): Hook {
const hook: 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;
}
首先也是会为useState('value2')
创建一个hook对象,继续执行,此时workInProgressHook
指向的是useState('value1')
创建的hook对象,则会将useState('value2')
创建的对象挂载到useState('value1')
创建的hook对象下的next
属性上,形成一个hook对象链表
。
在挂载阶段hook创建到此也完成了。
组件更新
还是以之前的例子来讲,现在我们点击Button,调用了setNum
去更新state,上面讲过更新state的函数其实就是dispatchAction
,我们来看一下源码:
function dispatchAction<S, A>(
fiber: Fiber,
queue: UpdateQueue<S, A>,
action: A,
) {
...
const eventTime = requestEventTime(); // 获取更新触发时的时间
const lane = requestUpdateLane(fiber); // 获取更新优先级
// 创建更新对象
const update: Update<S, A> = {
lane,
action,
eagerReducer: null,
eagerState: null,
next: (null: any),
};
// 获取workInProgress树上对应的fiber
const alternate = fiber.alternate;
// currentlyRenderingFiber:是在render阶段调用函数组件前将函数组件对应的workInprogress的fiber赋值给了它,在调用函数组件后重置为null
// 如果fiber === currentlyRenderingFiber ,则说明该函数组件正在执行更新任务,将需要更新的数据添加到更新链表中即可,不用产生更新任务进行调度
if (
if (
fiber === currentlyRenderingFiber ||
(alternate !== null && alternate === currentlyRenderingFiber)
) {
...
} else {
if (isInterleavedUpdate(fiber, lane)) {
...
} else {
// 将创建对象连接形成一个环形链表
const pending = queue.pending;
if (pending === null) {
// This is the first update. Create a circular list.
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
}
if (
fiber.lanes === NoLanes &&
(alternate === null || alternate.lanes === NoLanes)
) {
const lastRenderedReducer = queue.lastRenderedReducer;
if (lastRenderedReducer !== null) {
let prevDispatcher;
...
try {
const currentState: S = (queue.lastRenderedState: any);
// 获取最新的state
const eagerState = lastRenderedReducer(currentState, action);
update.eagerReducer = lastRenderedReducer;
update.eagerState = eagerState;
// 判断上一次更新的值与本次更新的值是否相同,不同则执行更新,相同直接返回不会执行更新
if (is(eagerState, currentState)) {
return;
}
} catch (error) {
// Suppress the error. It will throw again in the render phase.
} finally {
if (__DEV__) {
ReactCurrentDispatcher.current = prevDispatcher;
}
}
}
}
...
}
可以看到dispatchAction
中主要是创建了一个更新对象,将更新对象挂载到queue
的pending
属性上,那有的同学可能有疑惑了,fiber
和queue
哪来的?还记的我们分析函数组件挂载的时候,最后将dispatchAction
函数通过bind
方法传入函数组件对应的fiber
对象和队列对象:
function mountState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
...
const queue = (hook.queue = {
pending: null, // 需要更新的state
interleaved: null,
lanes: NoLanes, // 更新的lane优先级
dispatch: null, // 发起更新state的函数
lastRenderedReducer: basicStateReducer, // 上一次计算state的函数
lastRenderedState: (initialState: any), // 上一次更新的state
});
const dispatch = (queue.dispatch = (dispatchAction.bind(
null,
currentlyRenderingFiber,
queue,
): any));
return [hook.memoizedState, dispatch];
}
所以我们在更新state时,能够获取到函数组件对应的fiber
对象和queue
对象。
将更新对象挂载到queue
的pending
属性上前,会将更新对象形成一个环形链表:
update1 update1.next -> update2 update2.next -> update1
之后调用lastRenderedReducer
函数获取最新的state值:
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
return typeof action === 'function' ? action(state) : action;
}
lastRenderedReducer
实际上就是basicStateReducer
函数,在它内部对action
也就是我们调用更新函数传入的值,判断它是不是函数,是函数则会传入上一次的值并调用该函数获取到最新的值,如果不是函数,则直接返回新传入的值。
然后将最新的state,赋值给更新对象的eagerState
属性上。
接着对eagerState(本次更新的state)
和currentState(上一次更新的state)
使用Object.is
进行对比,如果相等,则会return
不会进行更新,反之,则会进行更新。
当进行更新时,则又会调用renderWidthHooks
函数。
给ReactCurrentDispatcher.current
赋值为HooksDispatcherOnUpdate
。
然后调用函数组件,这是函数组件内部的hook函数又会执行一遍,我们来看看此时又是如何执行的。
首先还是会调用useState('value1')
,和挂载一样,但是不同的是,不再调用mountState
函数,而是调用updateState
函数:
function updateState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
return updateReducer(basicStateReducer, (initialState: any));
}
而updateState
内部调用了updateReducer
函数:
function updateReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
const hook = updateWorkInProgressHook();
...
}
在updateReducer
内部首先调用了updateWorkInProgressHook
去获取对应的hook对象:
function updateWorkInProgressHook(): Hook {
// currentlyRenderingFiber是workInprogress树上的Fiber
// currentlyRenderingFiber.alternate则是对应在current树上的Fiber
// nextCurrentHook的值则是current树上的Fiber的hook链表
let nextCurrentHook: null | Hook;
if (currentHook === null) {
const current = currentlyRenderingFiber.alternate;
if (current !== null) {
nextCurrentHook = current.memoizedState;
} else {
nextCurrentHook = null;
}
} else {
nextCurrentHook = currentHook.next;
}
// nextWorkInProgressHook的值则是workInProgress树上的Fiber的hook链表
let nextWorkInProgressHook: null | Hook;
if (workInProgressHook === null) {
nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
} else {
nextWorkInProgressHook = workInProgressHook.next;
}
// 将currentHook作为current树上的Fiber的hook链表的指针
// 将currentHook作为workInProgressHook作为对应workInProgress树上的Fiber的hook链表的指针
if (nextWorkInProgressHook !== null) {
// There's already a work-in-progress. Reuse it.
workInProgressHook = nextWorkInProgressHook;
nextWorkInProgressHook = workInProgressHook.next;
currentHook = nextCurrentHook;
} else {
// Clone from the current hook.
currentHook = nextCurrentHook;
const newHook: Hook = {
memoizedState: currentHook.memoizedState,
baseState: currentHook.baseState,
baseQueue: currentHook.baseQueue,
queue: currentHook.queue,
next: null,
};
if (workInProgressHook === null) {
currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
} else {
workInProgressHook = workInProgressHook.next = newHook;
}
}
return workInProgressHook;
}
我们知道每次调用完函数组件后都会将currentHook
和workInProgressHook
都重置为null。
- 会将
nextCurrentHook
赋值为current树上函数组件对应fiber上的memoizedState
(hook对象链接)属性,也可以理解为,将nextCurrentHook
指向为current树上函数组件的hook链表的第一个hook对象。 nextWorkInProgressHook
赋值为workInProgress树上函数组件对应fiber上的memoizedState
(hook对象链接)属性,但是在每次调用函数组件前,都会将函数组件在workInProgress树上对应fiber的memoizedState
置为null,所以nextWorkInProgressHook
第一次总是为null。nextWorkInProgressHook
为null,则会根据currentHook创建一个新的hook对象,然后将新的对象赋值给workInProgressHook
,并挂载在函数组件在workInProgress树上对应fiber的memoizedState
属性上。- 将
nextCurrentHook
赋值给currentHook
- 返回
workInProgressHook
通过这个函数我们发现有两个指针:currentHook
,workInProgressHook
:
currentHook
:指向的是current树上函数组件对应的hook链表上的hook对象workInProgressHook
:指向的是workInProgress树上函数组件对应的hook链表上的hook对象
并且它们两个指针指向的hook对象是一一对应的:此时currentHook
指向的是current树上函数组件对应的hook链表上的第一个对象useState('value1')
,workInProgressHook
指向的是根据currentHook
指向的hook对象创建出来的新的hook对象,也是useState('value1')
。
updateWorkInProgressHook
调用完成后,通过currentHook
可以拿到上一次的state值,通过遍历更新对象形成的环形链表计算出最终的state。
最后也会返回一个集合,第一个元素是最新state值,第二个元素是更新函数。
为什么需要将hook函数放在函数组件的顶层
举个例子:
const [num, setNum] = useState('value1'); // useState1
const [num2, setNum2] = useState('value2'); // useState2
现在创建完成后的hook链表是这样:
useState1 -> useState2
我们现在将useState1放在if里面,让它在更新阶段不执行:
let isMount = false;
// 只在创建阶段执行,更新阶段不执行
if(!isMount) {
const [num, setNum] = useState('value1'); // useState1
isMount = true;
}
const [num2, setNum2] = useState('value2'); // useState2
然后我们更新useState2
的值:
setNum2('update');
在更新阶段,会根据current上的hook链表创建workInprogress的hook链表,虽然useState1
不会执行,但是在执行useState2
的时候,会去获取current上的hook链表的第一个对象useState1-hook
,以此创建useState2
在workInprogress的hook链表上的hook对象,所以此时useState2
的hook对象是useState1-hook
,但是更新操作是在useState2
上发起的,useState1
并不会更新,所以会获取useState1
上一次更新的值:value1
,然后返回给useState2
,最终useState2
得到更新的值则为:value1
。
hook链表:
curent树上的hook链表: useState1 hook -> useState2 hook
workInprogress树上的hook链表: useState1 hook
之所以结果会是这样,是因为在创建时,会根据hook函数书写的顺序创建hook链表,在更新时,使用指针从hook链表中依次取出对应的hook对象进行执行,将hook函数放入了if中相当于将后面的hook函数的执行提前,与hook链表中所对应的hook对象的位置就错乱了,则会造成无法预期的结果。
总结
- 在挂载函数组件时,会根据hook函数书写的位置创建一个hooks链表,挂载在函数组件对应的fiber的
memoizedState
属性上。 - 在更新函数时,使用
currentHook
指针依次取出函数组件对应在current树上fiber的hooks链表中的hook对象,然后根据currentHook
来创建或复用,以此创建workInprogress树上函数组件的hooks链表,并且会使用workInprogressHook
指针取出hook对象。workInprogressHook
与currentHook
是一一对应的。 - 使用两个指针:
currentHook
,workInprogressHook
的目的是,使用currentHook
可以获取上一次hook函数更新的状态,协助本次更新,比如:- 使用了
useEffect
,上一次传入的依赖数组为:[a=1]
,本次更新依赖数组为[a=2]
, 两次对比不同则需要执行回调函数。 - 使用了
useState
,本次调用更新函数传入一个函数,我们知道回调函数会将上一次的值作为参数传入,那么这个参数从哪里来的?是的,从curent树上的hook链表中hook函数对应的hook对象上取出来的。
- 使用了