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