简介
版本为17.0.3,这篇文章之前需要对react 更新的机制有基本了解。
react遵循函数式编程的理念,但实际业务中使用的时候会因为state,接口调用等无法成为一个函数组件
参考hooks的官方文档,hooks的优点有:
- class在组件之间复用状态逻辑很难,hooks为共享状态逻辑提供更好的原生途径
- class复杂组件因为生命周期的逻辑变得难以理解,hooks将逻辑拆成更小单元,并提供useEffect之类托管副作用
- class学习成本高,Hook 使你在非 class 的情况下可以使用更多的 React 特性,更符合函数是编程的理念
Hooks如何挂载
对于函数组件(FunctionComponent)类型,在beginwork的时候会调用renderWithHooks注册,根据mount还是update调用一个函数生成hooks链表挂在Fiber上
function renderWithHooks(current, workInProgress, Component, props, secondArg, nextRenderLanes) {
renderLanes = nextRenderLanes;
currentlyRenderingFiber$1 = workInProgress;
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
workInProgress.lanes = NoLanes; // The following should have already been reset
{
if (current !== null && current.memoizedState !== null) {
ReactCurrentDispatcher$1.current = HooksDispatcherOnUpdateInDEV;
} else if (hookTypesDev !== null) {
ReactCurrentDispatcher$1.current = HooksDispatcherOnMountWithHookTypesInDEV;
} else {
ReactCurrentDispatcher$1.current = HooksDispatcherOnMountInDEV;
}
}
...
return children;
}
复制代码
hook对象属性
hook:{
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null
};
复制代码
mount
会让current指向HooksDispatcherOnMountInDEV,使调用的时候获取到正确的hooks,对象如下
update时跟mount也近似,hooks中对于链表的处理不太一样
HooksDispatcherOnMountInDEV = {
...
useRef: function (initialValue) {
currentHookNameInDev = 'useRef';
mountHookTypesDev();
return mountRef(initialValue);
},
useState: function (initialState) {
currentHookNameInDev = 'useState';
mountHookTypesDev();
var prevDispatcher = ReactCurrentDispatcher$1.current;
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnMountInDEV;
try {
return mountState(initialState);
} finally {
ReactCurrentDispatcher$1.current = prevDispatcher;
}
},
...
unstable_isNewReconciler: enableNewReconciler
};
复制代码
在functionComponent中调用hooks,会创建一个hook对象 ,挂在当前fiber的memoizedState上,如果已经有hook挂了,就会用next记录下个hooks位置形成一个链表,并且用workInProgressHook记录当前hook
update
对于更新时 由于fiber双缓存机制,current tree 跟workInProgress tree中都会有hooks链表,如果他为空会复制原先的链表,用current作为指针记录工作hook位置
function updateWorkInProgressHook() {
var nextCurrentHook;
if (currentHook === null) {
var current = currentlyRenderingFiber$1.alternate;
if (current !== null) {
nextCurrentHook = current.memoizedState;
} else {
nextCurrentHook = null;
}
} else {
nextCurrentHook = currentHook.next;
}
var nextWorkInProgressHook;
if (workInProgressHook === null) {
nextWorkInProgressHook = currentlyRenderingFiber$1.memoizedState;
} else {
nextWorkInProgressHook = workInProgressHook.next;
}
if (nextWorkInProgressHook !== null) {
workInProgressHook = nextWorkInProgressHook;
nextWorkInProgressHook = workInProgressHook.next;
currentHook = nextCurrentHook;
} else {
if (!(nextCurrentHook !== null)) {
{
throw Error( "Rendered more hooks than during the previous render." );
}
}
currentHook = nextCurrentHook;
var newHook = {
memoizedState: currentHook.memoizedState,
baseState: currentHook.baseState,
baseQueue: currentHook.baseQueue,
queue: currentHook.queue,
next: null
};
if (workInProgressHook === null) {
// This is the first hook in the list.
currentlyRenderingFiber$1.memoizedState = workInProgressHook = newHook;
} else {
// Append to the end of the list.
workInProgressHook = workInProgressHook.next = newHook;
}
}
return workInProgressHook;
}
复制代码
一些hooks例子源码解析
准备解析下 useEffect|useLayoutEffect 跟 useState ;
useEffect | useLayoutEffect
创建effect
调用pushEffect创建effect对象挂在hook的memoizedState上,并且挂在fiber的updateQueue当中,形成环形链表
结合之前创建更新hooks部分,有两个useEffect hooks最终类似于这么一个结构:
fiber.memoizedState = hook1.next=hook2
hook1.memoizedState = effect1.next=effect2
hook2.memoizedState = effect2.next=effect1
fiber.updateQueue = effect1.next=effect2 // 跟class/hostComponent存储的updater对象不同
function pushEffect(tag, create, destroy, deps) {
var effect = {
tag: tag, // effect的类型,用于区分同步调用还是调度调用,useEffect useLayoutEffect
create: create, // 回调函数,类似class组件的componentDidmount/update 时调用
destroy: destroy, // 回调函数return的函数 类似class组件的componentWIllUnmount执行
deps: deps, // 依赖项
// Circular
next: null // 环形链表
};
var componentUpdateQueue = currentlyRenderingFiber$1.updateQueue;
if (componentUpdateQueue === null) {
componentUpdateQueue = createFunctionComponentUpdateQueue();
currentlyRenderingFiber$1.updateQueue = componentUpdateQueue;
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
var lastEffect = componentUpdateQueue.lastEffect;
if (lastEffect === null) {
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
var firstEffect = lastEffect.next;
lastEffect.next = effect;
effect.next = firstEffect;
componentUpdateQueue.lastEffect = effect;
}
}
return effect;
}
复制代码
更新effect
逻辑跟上面差不多,不同的是取了当前树的destroy,因为每次对于组件都要先执行销毁,再更新,还对比了deps,监视数组中的值有没有变化,有才会把调用pushEffect去执行,
function updateEffectImpl(fiberFlags, hookFlags, create, deps) {
var hook = updateWorkInProgressHook();
var nextDeps = deps === undefined ? null : deps;
var destroy = undefined;
if (currentHook !== null) {
var prevEffect = currentHook.memoizedState;
destroy = prevEffect.destroy;
if (nextDeps !== null) {
var prevDeps = prevEffect.deps;
if (areHookInputsEqual(nextDeps, prevDeps)) {
hook.memoizedState = pushEffect(hookFlags, create, destroy, nextDeps);
return;
}
}
}
currentlyRenderingFiber$1.flags |= fiberFlags;
hook.memoizedState = pushEffect(HasEffect | hookFlags, create, destroy, nextDeps);
}
复制代码
调度&使用
commit函数比较复杂,分为三个阶段去看: mutation之前, mutation , layout
useEffect在 mutation之前阶段交给scheduleCallback调度,layout阶段执行。
mutation之前执行flushPassiveEffects,因为NormalPriority优先级较低,每次commit之前需要把之前的effect执行,然后调度执行。
在flushPassiveEffects方法内部,会遍历rootWithPendingPassiveEffects(即effectList)执行effect回调函数。
effectList:遍历Fiber树时,由beginWork为Fiber打上Placement Update Deletion标签(Fiber.flag,以前叫effect),在completeWork阶段收集起来的链表。
function commitRootImpl(root, renderPriorityLevel) {
do {
flushPassiveEffects();
} while (rootWithPendingPassiveEffects !== null);
...
// before mutation阶段在scheduleCallback中调度flushPassiveEffects
// layout阶段之后将effectList赋值给rootWithPendingPassiveEffects
// scheduleCallback触发flushPassiveEffects,flushPassiveEffects内部遍历rootWithPendingPassiveEffects
if ((finishedWork.subtreeFlags & PassiveMask) !== NoFlags || (finishedWork.flags & PassiveMask) !== NoFlags) {
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
scheduleCallback(NormalPriority, function () {
flushPassiveEffects();
return null;
});
}
}
...
// 遍历effectlist 对dom进行增删改查,
//对于updateEffect funComponent 执行useLayoutEffect销毁函数
// 调用componentWillUnmount钩子
// 对deletionEffect 执行删除操作并执行seEffect 回调。去除ref
commitMutationEffects(root, finishedWork, lanes);
...
// 执行layoutEffect
commitLayoutEffects(finishedWork, root, lanes);
requestPaint();
}
复制代码
useLayoutEffect:
会在上述的commit函数中的mutation时调用commitHookEffectListUnmount,作用为销毁,执行updateQueue上的useLayoutEffect的effect的destroy方法
function commitHookEffectListUnmount(flags, finishedWork, nearestMountedAncestor) {
var updateQueue = finishedWork.updateQueue;
var lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
if (lastEffect !== null) {
var firstEffect = lastEffect.next;
var effect = firstEffect;
do {
if ((effect.tag & flags) === flags) {
// Unmount
var destroy = effect.destroy;
effect.destroy = undefined;
if (destroy !== undefined) {
safelyCallDestroy(finishedWork, nearestMountedAncestor, destroy);
}
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
复制代码
接下来commit函数执行到layout阶段,commitLayoutEffects会调commitLayoutEffects_begin,对于有flags(即effect)的fiber,执行commitLayoutEffectOnFiber,他实际上根据tag执行commitHookEffectListMount方法,并会在tag等于class组件时执行didmount的生命周期,更新ref。
commitHookEffectListMount方法会调用create 新建一个destroy。
function commitHookEffectListMount(tag, finishedWork) {
var updateQueue = finishedWork.updateQueue;
var lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
if (lastEffect !== null) {
var firstEffect = lastEffect.next;
var effect = firstEffect;
do {
if ((effect.tag & tag) === tag) {
// Mount
var create = effect.create;
effect.destroy = create();
// error处理...
}
}
}
}
复制代码
useEffect:
flushPassiveEffects函数执行useEffect,他的作用是在处理effect时降低任务优先级项,最后再改回来,然后执行flushPassiveEffectsImpl,由于是调度进行会对已经删除的Fiber做处理,最后也会执行上述的commitHookEffectListUnmount,commitHookEffectListMount