react原理:函数组件的更新,hooks原理
这篇文章将函数组件的更新,也就是hooks的原理。
函数组件更新的入口为updateFunctionComponent,这个方法主要就是调用了renderWithHooks,而renderWithHooks主要做了两件事:确定hooksDispatcher,执行函数组件的代码
function renderWithHooks(current, workInProgress, Component, props, secondArg, nextRenderLanes) {
// ...
// 确定dispatcher
if (current !== null && current.memoizedState !== null) {
// 更新时使用update的dispatcher
ReactCurrentDispatcher$1.current = HooksDispatcherOnUpdateInDEV;
} else if (hookTypesDev !== null) {
ReactCurrentDispatcher$1.current = HooksDispatcherOnMountWithHookTypesInDEV;
} else {
// 挂载时使用mount的dispatcher
ReactCurrentDispatcher$1.current = HooksDispatcherOnMountInDEV;
}
// 执行组件代码
var children = Component(props, secondArg)
// 如果在render阶段更新了state,会造成重新渲染
// 下面的while循环就是防止太多次数的重新渲染
if (didScheduleRenderPhaseUpdateDuringThisPass) {
var numberOfReRenders = 0;
do {
didScheduleRenderPhaseUpdateDuringThisPass = false;
if (!(numberOfReRenders < RE_RENDER_LIMIT)) {
{
throw Error( "Too many re-renders. React limits the number of renders to prevent an infinite loop." );
}
}
numberOfReRenders += 1;
{
ignorePreviousDependencies = false;
}
currentHook = null;
workInProgressHook = null;
workInProgress.updateQueue = null;
{
hookTypesUpdateIndexDev = -1;
}
ReactCurrentDispatcher$1.current = HooksDispatcherOnRerenderInDEV ;
children = Component(props, secondArg);
} while (didScheduleRenderPhaseUpdateDuringThisPass);
}
// ...
return children
}
dispatcher是一个对象,里面定义了useState,useEffect等hooks。
hooks的数据结构
在讲解useState之前,先看一下hooks的数据结构:
类组件的
memoizedState保存更新后的state,函数组件的memoizedState保存hooks链表
fiber: {
memoizedState: {
hook: {
baseQueue: null,
baseState: null,
memoizedState: null,
queue: {
pending: null,
dispatch: null,
lastRenderedReducer: null,
lastRenderedState: null
},
next: hook
}
hook.baseQueue和类组件的firstBaseUpdate和lastBaseUpdate功能一致,memoizedState就是更新之后的state,queue.pending就是产生的更新队列,也是一个环形链表。
此外,workInProgressHook指向当前执行到了哪一个hook对象。
在执行函数组件的代码时,会遇到useState。而在挂载和更新时,useState有不同的逻辑,下面一一看一下
挂载时
看一下HooksDispatcherOnMountInDEV的useState方法,useState代码如下
useState: function (initialState) {
currentHookNameInDev = 'useState';
mountHookTypesDev();
var prevDispatcher = ReactCurrentDispatcher$1.current;
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnMountInDEV;
try {
return mountState(initialState);
} finally {
ReactCurrentDispatcher$1.current = prevDispatcher;
}
}
可以看到,mountState就是主要的函数
function mountState(initialState) {
var hook = mountWorkInProgressHook();
if (typeof initialState === 'function') {
initialState = initialState();
}
hook.memoizedState = hook.baseState = initialState;
var queue = hook.queue = {
pending: null,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: initialState
};
var dispatch = queue.dispatch = dispatchAction.bind(null, currentlyRenderingFiber$1, queue);
return [hook.memoizedState, dispatch];
}
mountWorkInProgressHook会新建一个hook对象,之后将hook对象添加到hooks链表中
function mountWorkInProgressHook() {
var hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null
};
if (workInProgressHook === null) {
// This is the first hook in the list
currentlyRenderingFiber$1.memoizedState = workInProgressHook = hook;
} else {
// Append to the end of the list
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
hook对象挂载完毕后,就会初始化hook.queue,返回一个数组,第一项是state,第二项是dispatch,也就是更新state的函数。至此,挂载时的useState流程讲解完毕。
更新时
通过触发state的更新方法,也就是dispatch,会触发函数组件的更新。下面看一下dipatch的实现。dispatch就是dispatchAction。下面看一下dispatchAction的实现
function dispatchAction(fiber, queue, action) {
// ...
var eventTime = requestEventTime();
var lane = requestUpdateLane(fiber);
// 创建更新
var update = {
lane: lane,
action: action,
eagerReducer: null,
eagerState: null,
next: null
};
// 将update放入queue.pending(环形链表)中
var pending = queue.pending;
if (pending === null) {
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
var alternate = fiber.alternate;
if (fiber === currentlyRenderingFiber$1 || alternate !== null && alternate === currentlyRenderingFiber$1) {
// 在render阶段触发的更新,标记didScheduleRenderPhaseUpdateDuringThisPass为true
// 比如在函数组件中直接调用useState的更新方法
didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
} else {
// 优化路径,暂不讲解
if (fiber.lanes === NoLanes && (alternate === null || alternate.lanes === NoLanes)) {
var lastRenderedReducer = queue.lastRenderedReducer;
if (lastRenderedReducer !== null) {
var prevDispatcher;
{
prevDispatcher = ReactCurrentDispatcher$1.current;
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
}
try {
var currentState = queue.lastRenderedState;
var eagerState = lastRenderedReducer(currentState, action);
update.eagerReducer = lastRenderedReducer;
update.eagerState = eagerState;
if (objectIs(eagerState, currentState)) {
return;
}
} catch (error) {
} finally {
{
ReactCurrentDispatcher$1.current = prevDispatcher;
}
}
}
}
{
if ('undefined' !== typeof jest) {
warnIfNotScopedWithMatchingAct(fiber);
warnIfNotCurrentlyActingUpdatesInDev(fiber);
}
}
// 调度更新
scheduleUpdateOnFiber(fiber, lane, eventTime);
}
}
scheduleUpdateOnFiber会调用ensureRootIsScheduled,进入render阶段,调用renderWithHooks。如果在render阶段触发了更新,会进入renderWithHooks中那个很长的if逻辑,判断是否有太多次的重新渲染。
进入renderWithHooks后,由于是组件的更新,因此ReactCurrentDispatcher$1为update的dispatcher,之后执行函数组件代码,再次遇到了useState,会调用dispatcher的useState
useState: function (initialState) {
currentHookNameInDev = 'useState';
updateHookTypesDev();
var prevDispatcher = ReactCurrentDispatcher$1.current;
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
try {
return updateState(initialState);
} finally {
ReactCurrentDispatcher$1.current = prevDispatcher;
}
}
updateState会调用updateReducer,传入的参数为basicStateReducer
function basicStateReducer(state, action) {
return typeof action === 'function' ? action(state) : action;
}
下面看updateReducer的实现
function updateReducer(reducer, initialArg, init) {
var hook = updateWorkInProgressHook();
var queue = hook.queue;
if (!(queue !== null)) {
{
throw Error( "Should have a queue. This is likely a bug in React. Please file an issue." );
}
}
queue.lastRenderedReducer = reducer;
var current = currentHook;
var baseQueue = current.baseQueue;
var pendingQueue = queue.pending;
if (pendingQueue !== null) {
if (baseQueue !== null) {
var baseFirst = baseQueue.next;
var pendingFirst = pendingQueue.next;
baseQueue.next = pendingFirst;
pendingQueue.next = baseFirst;
}
{
if (current.baseQueue !== baseQueue) {
error('Internal error: Expected work-in-progress queue to be a clone. ' + 'This is a bug in React.');
}
}
current.baseQueue = baseQueue = pendingQueue;
queue.pending = null;
}
// ...
var dispatch = queue.dispatch;
return [hook.memoizedState, dispatch];
}
在看updateWorkInProgressHook的源码之前,先看一下更新流程,方便理解
函数组件内使用了三次useState,则updateWorkInProgressHook的执行流程为
初始
current.memoizedState: hook1 -> hook2 -> hook3
workInProgress.memoizedState: null
遇到第一个useState,currentHook和workInProgressHook均为null,先确定nextCurrentHook和nextWorkInProgressHook
current.memoizedState: hook1 -> hook2 -> hook3,currentHook为null,nextCurrentHook为hook1
workInProgress.memoizedState: null,workInProgressHook为null,nextWorkInProgressHook也为null
之后更新currentHook为nextCurrentHook,根据currentHook更新workInProgressHook
workInProgress.memoizedState变为 hook1(wip)
遇到第二个useState,currentHook为hook1,workInProgressHook为hook1(wip),nextCurrentHook为hook2
更新currentHook为hook2,根据currentHook创建workInProgressHook
current.memoizedState: hook1 -> hook2 -> hook3
workInProgress.memoizedState变为 hook1(wip) -> hook2(wip)
。。。
function updateWorkInProgressHook() {
// currentHook就是current树的fiber节点上的hook对象
// 在更新阶段,current树的hooks链表已经存在,因此会根据currentHook创建workInProgress的hook对象
var nextCurrentHook;
// 确定nextCurrentHook
if (currentHook === null) {
// 当前hook是函数组件中的第一个hook
// currentlyRenderingFiber$1就是workInProgress
var current = currentlyRenderingFiber$1.alternate;
if (current !== null) {
nextCurrentHook = current.memoizedState;
} else {
nextCurrentHook = null;
}
} else {
nextCurrentHook = currentHook.next;
}
// nextWorkInProgressHook的更新
var nextWorkInProgressHook;
if (workInProgressHook === null) {
// 当前hook是函数组件中的第一个hook
nextWorkInProgressHook = currentlyRenderingFiber$1.memoizedState;
} else {
nextWorkInProgressHook = workInProgressHook.next;
}
if (nextWorkInProgressHook !== null) {
// 进入该分支,说明在render产生了更新
workInProgressHook = nextWorkInProgressHook;
nextWorkInProgressHook = workInProgressHook.next;
// 更新currentHook
currentHook = nextCurrentHook;
} else {
if (!(nextCurrentHook !== null)) {
{
throw Error( "Rendered more hooks than during the previous render." );
}
}
// 更新currentHook
currentHook = nextCurrentHook;
var newHook = {
memoizedState: currentHook.memoizedState,
baseState: currentHook.baseState,
baseQueue: currentHook.baseQueue,
queue: currentHook.queue,
next: null
};
if (workInProgressHook === null) {
currentlyRenderingFiber$1.memoizedState = workInProgressHook = newHook;
} else {
workInProgressHook = workInProgressHook.next = newHook;
}
}
return workInProgressHook;
}
在render阶段产生更新可以看下面的代码
export default function App() {
const [count, setCount] = useState(1)
const [num, setNum] = useState(10)
setNum((prev) => prev + 1)
return (
<div>
<button onClick={() => setCount((prevCount) => setCount(prevCount + 1))}>
change count
</button>
<button onClick={() => setNum((prevNum) => setNum(prevNum + 10))}>
change num
</button>
</div>
)
}
注意,如果在render产生了更新,hooks链表并不会无限向后延长,而是会重新遍历。因为每次执行完函数组件的代码后,都会将currentHook和workInProgressHook置为null
function renderWithHooks(current, workInProgress, Component, props, secondArg, nextRenderLanes) {
// ...确定dispatcher
// 执行函数组件代码
var children = Component(props, secondArg);
// 。。。render阶段触发更新判断
currentHook = null;
workInProgressHook = null;
// ...
}
执行完updateWorkInProgressHook后,回到updateReducer,接下来进入到状态更新计算的部分。这部分与类组件的状态计算原理一致,只是实现上稍有不同
function updateReducer(reducer, initialArg, init) {
// ...
queue.lastRenderedReducer = reducer;
var current = currentHook;
var baseQueue = current.baseQueue;
var pendingQueue = queue.pending;
if (pendingQueue !== null) {
// 将两条链表合并
// 注意,这里没有将pending的环形链表剪断
// 而是将baseQueue拼接到了pending的后面,依旧是环形链表
// 不明白的可以看下面的图
if (baseQueue !== null) {
var baseFirst = baseQueue.next;
var pendingFirst = pendingQueue.next;
baseQueue.next = pendingFirst;
pendingQueue.next = baseFirst;
}
{
if (current.baseQueue !== baseQueue) {
error('Internal error: Expected work-in-progress queue to be a clone. ' + 'This is a bug in React.');
}
}
current.baseQueue = baseQueue = pendingQueue;
queue.pending = null;
}
if (baseQueue !== null) {
// 从baseQueue开始遍历环形链表
var first = baseQueue.next;
var newState = current.baseState;
var newBaseState = null;
var newBaseQueueFirst = null;
var newBaseQueueLast = null;
var update = first;
do {
var updateLane = update.lane;
if (!isSubsetOfLanes(renderLanes, updateLane)) {
// 优先级不足
var clone = {
lane: updateLane,
action: update.action,
eagerReducer: update.eagerReducer,
eagerState: update.eagerState,
next: null
};
if (newBaseQueueLast === null) {
newBaseQueueFirst = newBaseQueueLast = clone;
newBaseState = newState;
} else {
newBaseQueueLast = newBaseQueueLast.next = clone;
}
currentlyRenderingFiber$1.lanes = mergeLanes(currentlyRenderingFiber$1.lanes, updateLane);
markSkippedUpdateLanes(updateLane);
} else {
// 优先级足够
if (newBaseQueueLast !== null) {
var _clone = {
lane: NoLane,
action: update.action,
eagerReducer: update.eagerReducer,
eagerState: update.eagerState,
next: null
};
newBaseQueueLast = newBaseQueueLast.next = _clone;
}
if (update.eagerReducer === reducer) {
newState = update.eagerState;
} else {
var action = update.action;
newState = reducer(newState, action);
}
}
update = update.next;
// 当前链表为环形链表,终止条件为链表遍历完毕
} while (update !== null && update !== first);
if (newBaseQueueLast === null) {
newBaseState = newState;
} else {
newBaseQueueLast.next = newBaseQueueFirst;
}
if (!objectIs(newState, hook.memoizedState)) {
markWorkInProgressReceivedUpdate();
}
hook.memoizedState = newState;
hook.baseState = newBaseState;
hook.baseQueue = newBaseQueueLast;
queue.lastRenderedState = newState;
}
var dispatch = queue.dispatch;
return [hook.memoizedState, dispatch];
}
baseQueue和pending的拼接方式如下图所示

总结
至此,函数组件的状态更新原理也讲解完毕。可以发现,其实仅针对与useState来说,函数组件和类组件差别不大,只是state的实现方式有所不同,真正体现函数组件和类组件区别的地方,个人认为还是在js语言本身的特性上。
在执行完renderWithHooks方法后,会进入reconcile阶段,类组件和函数组件的这部分内容是相同的,之后会统一讲解。
转载自:https://juejin.cn/post/6987766180949966878