以下源码浅析的React版本为17.0.1,使用ReactDOM.render
创建的同步应用,不含优先级相关。
流程简述
函数组件会调用renderWithHooks
函数,这个函数主要会标记当前渲染的currentlyRenderingFiber
节点,并判断该使用哪一个HooksDispatcher
(React里Mount和Update所使用的Hooks不是同一个),接着执行此函数组件,分别处理hook
函数,并得到children
,将children
返回到上层函数后,执行reconcileChildren
生成child
子节点。
renderWithHooks
renderWithHooks
函数为函数组件的入口函数,无论是Mount时的mountIndeterminateComponent
还是Update时的updateFunctionComponent
都会进入这个函数来获取函数组件的children
。
export function renderWithHooks<Props, SecondArg>(
current: Fiber | null,
workInProgress: Fiber,
Component: (p: Props, arg: SecondArg) => any,
props: Props,
secondArg: SecondArg,
nextRenderLanes: Lanes,
): any {
// 省略代码...
currentlyRenderingFiber = workInProgress;
// 使用mount还是update的hook
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
// 省略代码...
// 执行函数后返回的children
let children = Component(props, secondArg);
// 省略代码...
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
// 省略代码...
return children;
}
renderWithHooks
中会将当前的workInProgress
Fiber节点存在全局变量currentlyRenderingFiber
中,这样方便后面获取Fiber信息,接着在调用函数获取children
之前,先判断使用哪一个ReactCurrentDispatcher
,最后返回children
给上层函数处理。
需要注意的是Mount时和Update时用的不是同一个hook。
const HooksDispatcherOnMount: Dispatcher = {
// ...
useReducer: mountReducer,
useState: mountState,
};
const HooksDispatcherOnUpdate: Dispatcher = {
// ...
useReducer: updateReducer,
useState: updateState,
};
再来看下useState
export function useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
// 返回的是调用函数组件之前赋值的ReactCurrentDispatcher.current
function resolveDispatcher() {
const dispatcher = ReactCurrentDispatcher.current;
return dispatcher;
}
所以后续会分为Mount和Update来分析
Hooks的数据结构
单个hook
的数据结构如下
type Hook = {|
memoizedState: any, // 对于useState和useReducer的值就是state的值
baseState: any,
baseQueue: Update<any, any> | null,
queue: UpdateQueue<any, any> | null, // 更新队列
next: Hook | null, // 下一个hook节点
|}
那函数组件如何找到对应的hooks
信息呢?函数组件的hooks
的信息储存在对应Fiber节点中的memoizedState
字段中,代表函数组件里的第一个hook
,hooks
数据结构为单向链表,每一个节点可以通过next
属性找到下一个hook
。
function Container(){
React.useState() // currentlyRenderingFiber.memoizedState (数据结构即是上面的Hook type)
React.useState() // currentlyRenderingFiber.memoizedState.next (Hook.next)
// ...
}
每一个hook
的更新队列都会存在queue
字段里,通过执行队列里的操作就可以得到最新的state
值,结构如下
type UpdateQueue<S, A> = {|
pending: Update<S, A> | null, // 最新的更新任务
dispatch: (A => mixed) | null, // 也就是useState的第二个参数用来发起更新
lastRenderedReducer: ((S, A) => S) | null, // 上一次的reducer
lastRenderedState: S | null, // 上次的state值
|};
type Update<S, A> = {|
lane: Lane,
action: A,
eagerReducer: ((S, A) => S) | null,
eagerState: S | null, // 第一次调用dispatch时赋值
next: Update<S, A>, // 下一个更新
priority?: ReactPriorityLevel,
|};
hook
的更新队列是一个单向环形链表,pending
字段保存的是最新的update
,通过update.next
可以获取第一个加入更新队列的update
。
update2(pending) -> update0 -> update1 -> update2
useState和useReducer
useState
实际是一个自带了reducer
的useReducer
语法糖,所以需要放在一起分析。
mountState
function mountState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
// 创建一个新的hook结构,如果workInProgressHook已经存在就用next连接起来
const hook = mountWorkInProgressHook();
// useState的初始值
if (typeof initialState === 'function') {
initialState = initialState();
}
hook.memoizedState = hook.baseState = initialState;
const queue = (hook.queue = {
pending: null,
dispatch: null,
lastRenderedReducer: basicStateReducer, // 自带了一个basicStateReducer
lastRenderedState: (initialState: any),
});
const dispatch: Dispatch<
BasicStateAction<S>,
> = (queue.dispatch = (dispatchAction.bind(
null,
currentlyRenderingFiber,
queue,
): any));
return [hook.memoizedState, dispatch];
}
// useState自带的reducer
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
return typeof action === 'function' ? action(state) : action;
}
mountState
自带了一个basicStateReducer
mountReducer
function mountReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
const hook = mountWorkInProgressHook();
let initialState;
// useReducer的初始值
if (init !== undefined) {
initialState = init(initialArg);
} else {
initialState = ((initialArg: any): S);
}
hook.memoizedState = hook.baseState = initialState;
const queue = (hook.queue = {
pending: null,
dispatch: null,
lastRenderedReducer: reducer, // 传入的reducer
lastRenderedState: (initialState: any),
});
const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(
null,
currentlyRenderingFiber,
queue,
): any));
return [hook.memoizedState, dispatch];
}
mountState
和mountReducer
的区别在于函数参数和初始值赋值的不同,其他都是一样的。
进入函数首先会通过mountWorkInProgressHook
来创建hook
对象,接着初始化queue
,通过bind
将Fiber节点和queue
传入dispatchAction
函数实现部分参数,最后将值返回。
mountWorkInProgressHook
mountWorkInProgressHook
函数功能比较简单,创建一个hook
对象,将多个hook
对象连接为单向链表。
function mountWorkInProgressHook(): Hook {
// 创建新的hook对象
const hook: Hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null,
};
if (workInProgressHook === null) {
// workInProgressHook为null,则当前hook为函数里第一个hook,赋值给Fiber节点的memoizedState
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
// 后续的hook就通过next来连接
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
dispatchAction
dispatchAction
方法为用来创建一个新的update,来发起更新
/* mountReducer ...
const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(
null,
currentlyRenderingFiber,
queue,
): any));
*/
// 每次调用之前会bind上fiber和queue参数,只需要传入action
function dispatchAction<S, A>(
fiber: Fiber,
queue: UpdateQueue<S, A>,
action: A,
) {
// 省略代码...
const update: Update<S, A> = {
lane,
action,
eagerReducer: null,
eagerState: null,
next: (null: any),
};
// 环型链表
// 将update添加到链表最后
const pending = queue.pending;
if (pending === null) {
// pending为null的时候是第一次调用dispatchAction发起更新
// 创建一个环形链表
update.next = update;
} else {
// 将后续添加的update
update.next = pending.next;
pending.next = update;
}
// pending的值为最新的update
queue.pending = update;
const alternate = fiber.alternate;
// 判断是否为render阶段的更新
if (
fiber === currentlyRenderingFiber ||
(alternate !== null && alternate === currentlyRenderingFiber)
) {
// 标记
didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
} else {
// 通过优先级判断是否为第一次更新(?)
if (
fiber.lanes === NoLanes &&
(alternate === null || alternate.lanes === NoLanes)
) {
// 当前没有更新队列,是第一次调用dispatchAction产生update,所以可以在进入render阶段之前就计算出state
const lastRenderedReducer = queue.lastRenderedReducer;
if (lastRenderedReducer !== null) {
try {
const currentState: S = (queue.lastRenderedState: any);
const eagerState = lastRenderedReducer(currentState, action);
// 在render阶段判断reducer如果没变化就直接取eagerState的值
update.eagerReducer = lastRenderedReducer;
update.eagerState = eagerState;
if (is(eagerState, currentState)) {
// 如果结果和当前state值一致,就不需要发起更新调度了,如果不一致,则可以在render阶段获取eagerState来直接取值而不需要再次计算
return;
}
} catch (error) {
// 错误会在render阶段抛出
}
}
}
// 发起更新调度
scheduleUpdateOnFiber(fiber, lane, eventTime);
}
}
dispatchAction
里会把更新连接进环形链表,如果不是render
阶段的更新则会通过优先级判断Fiber节点上是否存在更新,如果不存在就会在dispatchAction
里计算出新的state
值,接着判断新旧值是否相同,相同就不需要发起更新调度了。
当同步调用多次dispatchAction
就会产生多个update
,会将他们组成环形链表。
function Updater(){
const render = useState(0)[1];
return <div onClick={()=>{
render(prev => prev+1);
render(prev => prev+1);
render(prev => prev+1);
}}>update</div>
}
这里的环形链表连接比较难理解
const pending = queue.pending;
if (pending === null) {
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
第一次执行的时候,pending === null
,会创建一个自己连接自己的环形链表,这里表示为u0 -> u0
。
第二次执行的时候,pending !== null
,创建一个新的更新u1
update.next = pending.next
即为u1.next = u0.next
,上一次执行时u0.next -> u0
,结果为u1.next = u0
pending.next = update
这时候pending
是u0
,即为u0.next = u1
queue.pending = update
即为queue.pending = u1
,这时候最终结果为queue.pending(u1) -> u0 -> u1
第三次执行的时候,创建一个新的更新u2
update.next = pending.next
即为u2.next = u1.next
,上一次执行时queue.pending(u1) -> u0 -> u1
,结果为u2.next = u0
pending.next = update
这时候pending
是u1
,即为u1.next = u2
queue.pending = update
即为queue.pending = u2
这里仔细思考,实际上只改变了头和尾,中间的连接(u0 - > u1
)没有改变,所以最后的结果为queue.pending(u2) -> u0 -> u1 -> u2
最终的环形链表的pending
始终指向最新的update
,而最新的update.next
指向第一个更新u0
updateState
updateState
调用的就完全是updateReducer
了,只是传入了自带的reducer
,所以updateState
和updateReducer
可谓是完全一致
function updateState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
return updateReducer(basicStateReducer, (initialState: any));
}
updateReducer
mountReducer
返回的值是initialState
,updateReducer
返回的值则是通过调用依次queue
中的update
计算后的state
值。
function updateReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
// update阶段对应的hook
const hook = updateWorkInProgressHook();
const queue = hook.queue;
queue.lastRenderedReducer = reducer;
// 与workInProgressHook对应的currentHook
const current: Hook = (currentHook: any);
let baseQueue = current.baseQueue;
const pendingQueue = queue.pending;
if (pendingQueue !== null) {
if (baseQueue !== null) {
// 省略代码...
}
// pendingQueue赋值给baseQueue
current.baseQueue = baseQueue = pendingQueue;
queue.pending = null;
}
if (baseQueue !== null) {
// queue.pending为最新的update,next则为第一个update
const first = baseQueue.next;
let newState = current.baseState;
let newBaseState = null;
let newBaseQueueFirst = null;
let newBaseQueueLast = null;
let update = first;
do {
const updateLane = update.lane;
if (!isSubsetOfLanes(renderLanes, updateLane)) {
// 省略优先级相关代码...
} else {
if (newBaseQueueLast !== null) {
// 省略代码...
}
// update.eagerReducer只有在第一次调用dispatchAction发起更新的时候才会赋值
// 当reducer没有发生变化的时候
if (update.eagerReducer === reducer) {
newState = ((update.eagerState: any): S);
} else {
// 不是第一次调用dispatchAction就计算新的state
const action = update.action;
newState = reducer(newState, action);
}
}
// 指向下一个更新
update = update.next;
} while (update !== null && update !== first);
if (newBaseQueueLast === null) {
newBaseState = newState;
} else {
newBaseQueueLast.next = (newBaseQueueFirst: any);
}
// 值不同才标识变化
if (!is(newState, hook.memoizedState)) {
markWorkInProgressReceivedUpdate();
}
// 赋值新的state
hook.memoizedState = newState;
hook.baseState = newBaseState;
hook.baseQueue = newBaseQueueLast;
queue.lastRenderedState = newState;
}
const dispatch: Dispatch<A> = (queue.dispatch: any);
return [hook.memoizedState, dispatch];
}
updateReducer
会依次将queue
中的update
放入reducer
中计算,最后将新的state
值赋值给hook.memoizedState
并返回。
updateWorkInProgressHook
updateWorkInProgressHook
和mountWorkInProgressHook
功能相似,会返回一个浅拷贝的hook
对象,更改currentHook
和workInProgressHook
的指向,同时连接一个新的workInProgressHook
链表。
function updateWorkInProgressHook(): Hook {
let nextCurrentHook: null | Hook;
// 第一次进入fiber节点的时候执行hook没有hook指向
if (currentHook === null) {
// 找到alternate fiber节点的memoizedState hook对象
const current = currentlyRenderingFiber.alternate;
if (current !== null) {
nextCurrentHook = current.memoizedState;
} else {
nextCurrentHook = null;
}
} else {
// 后续执行hook的alternate hook对象
nextCurrentHook = currentHook.next;
}
let nextWorkInProgressHook: null | Hook;
// 省略代码...
if (nextWorkInProgressHook !== null) {
// 省略代码...
} else {
currentHook = nextCurrentHook;
// 通过currentHook进行浅拷贝
const newHook: Hook = {
memoizedState: currentHook.memoizedState,
baseState: currentHook.baseState,
baseQueue: currentHook.baseQueue,
queue: currentHook.queue,
next: null,
};
// 组装workInProgressHook链表
if (workInProgressHook === null) {
currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
} else {
workInProgressHook = workInProgressHook.next = newHook;
}
}
return workInProgressHook;
}
总结
函数组件通过renderWithHooks
可以确定当前的WorkInProgressFiber
节点,通过是否存在currentFiber
节点来判断当前为Mount还是Update,分别获取不同的ReactCurrentDispatcher
,执行函数组件自己来获取children
。
执行过程中,同时会执行到对应的hook
函数,函数组件的hooks
为单向链表存在Fiber节点的memoizedState
字段上,通过hook.next
可以顺序依次获取hook
对象,每一个hook
对象中存在memoizedState
字段,对于useState
和useReducer
来说储存的即为state
值本身,hook
对象上存在queue
代表当前hook
的更新队列,为环形单向链表,queue.pending
指向为最新的update
,queue.pending.next
执行为第一个update
。
通过执行mountState
和mountReducer
来获取state
初始值,通过执行updateState
和updateReducer
来计算queue
中的update
以获取最新的state
值。
调用dispatchAction
发起更新调度,同时在dispatchAction
里会组装更新的queue
环形单向链表,最后在render
阶段会执行updateState
和updateReducer
来获取最新的state
值。
如有错误,还望交流指正。