前言
因为经常会用自定义hooks,也有很多前端同学对此有一些用法上的心得文章,我觉得都挺好的。
于是我相由心生,思考了这么一个问题:自定义hooks怎么触发上层组件更新的?
思考
换而言之,自定义hook函数是怎么找到上层组件,这层关联关系是怎样的?
方向
我最开始是有两个解决问题的方向:
- 去各大搜索引擎网站里去搜索,可惜很多文章都是以偏概全的去分析hook实现原理,体现不出细节,其实知识点应该是一点点积累,源码那么多,断掌取义出来的,只是一个个没有联系的节点,很难去把它们之间的关系说清楚。
- 看源码,自己调试。
选择
毫无疑问选择2
思路
注:为了更加具体,这里分析组件第一次挂载hook时的经过。
- 找到hook与fiber关系建立的地方
先找到HooksDispatcherOnMountInDEV这个dispatcher,初始化调用useState的时候会执行这个方法,可以看到执行了mountState这个方法。
HooksDispatcherOnMountInDEV = {
// ...
useState: function (initialState) {
console.log('%c⧭', 'color: #733d00', initialState);
currentHookNameInDev = 'useState';
mountHookTypesDev();
var prevDispatcher = ReactCurrentDispatcher$1.current;
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnMountInDEV;
try {
return mountState(initialState);
} finally {
ReactCurrentDispatcher$1.current = prevDispatcher;
}
},
}
接着看mountState这个方法,它返回了dispatch方法,即是修改hook state并触发组件更新的方法,这个地方dispatchAction.bind指向了当前fiber,这个地方便是建立hook state和对应上层组件的关键点!
function mountState(initialState) {
var hook = mountWorkInProgressHook();
if (typeof initialState === 'function') {
// $FlowFixMe: Flow doesn't like mixed types
initialState = initialState();
}
hook.memoizedState = hook.baseState = initialState;
var queue = (hook.queue = {
pending: null,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: initialState,
});
console.log('%c⧭', 'color: #917399', currentlyRenderingFiber$1);
var dispatch = (queue.dispatch = dispatchAction.bind(null, currentlyRenderingFiber$1, queue));
return [hook.memoizedState, dispatch];
}
所以问题来了,currentlyRenderingFiber$1是怎么来的呢?可以找到是在renderWithHooks这个方法里,将workInProgress赋值于它的。
function renderWithHooks(current, workInProgress, Component, props, secondArg, nextRenderLanes) {
renderLanes = nextRenderLanes;
currentlyRenderingFiber$1 = workInProgress;
console.log('%c renderWithHooks workInProgress ⧭', 'color: #d90000', workInProgress);
{
hookTypesDev = current !== null ? current._debugHookTypes : null;
hookTypesUpdateIndexDev = -1; // Used for hot reloading:
ignorePreviousDependencies = current !== null && current.type !== workInProgress.type;
}
// ...
}
workInProgress是怎么创建的呢,向上回溯,可以找到createWorkInProgress这个方法,所以我们来分析下这个方法:
workInProgress由createFiber方法创建,并修改了它的属性值。(createFiber方法的用途:可以创建不同类型的fiber)
我理解workInProgress它是一个个处理过程中的fiber。
function createWorkInProgress(current, pendingProps) {
var workInProgress = current.alternate;
if (workInProgress === null) {
// We use a double buffering pooling technique because we know that we'll
// only ever need at most two versions of a tree. We pool the "other" unused
// node that we're free to reuse. This is lazily created to avoid allocating
// extra objects for things that are never updated. It also allow us to
// reclaim the extra memory if needed.
workInProgress = createFiber(current.tag, pendingProps, current.key, current.mode);
workInProgress.elementType = current.elementType;
workInProgress.type = current.type;
workInProgress.stateNode = current.stateNode;
{
// DEV-only fields
workInProgress._debugID = current._debugID;
workInProgress._debugSource = current._debugSource;
workInProgress._debugOwner = current._debugOwner;
workInProgress._debugHookTypes = current._debugHookTypes;
}
workInProgress.alternate = current;
current.alternate = workInProgress;
} else {
workInProgress.pendingProps = pendingProps; // Needed because Blocks store data on type.
workInProgress.type = current.type; // We already have an alternate.
// Reset the effect tag.
workInProgress.flags = NoFlags; // The effect list is no longer valid.
workInProgress.nextEffect = null;
workInProgress.firstEffect = null;
workInProgress.lastEffect = null;
{
// We intentionally reset, rather than copy, actualDuration & actualStartTime.
// This prevents time from endlessly accumulating in new commits.
// This has the downside of resetting values for different priority renders,
// But works for yielding (the common case) and should support resuming.
workInProgress.actualDuration = 0;
workInProgress.actualStartTime = -1;
}
}
workInProgress.childLanes = current.childLanes;
workInProgress.lanes = current.lanes;
workInProgress.child = current.child;
workInProgress.memoizedProps = current.memoizedProps;
workInProgress.memoizedState = current.memoizedState;
workInProgress.updateQueue = current.updateQueue; // Clone the dependencies object. This is mutated during the render phase, so
// it cannot be shared with the current fiber.
var currentDependencies = current.dependencies;
workInProgress.dependencies =
currentDependencies === null
? null
: {
lanes: currentDependencies.lanes,
firstContext: currentDependencies.firstContext,
}; // These will be overridden during the parent's reconciliation
workInProgress.sibling = current.sibling;
workInProgress.index = current.index;
workInProgress.ref = current.ref;
{
workInProgress.selfBaseDuration = current.selfBaseDuration;
workInProgress.treeBaseDuration = current.treeBaseDuration;
}
{
workInProgress._debugNeedsRemount = current._debugNeedsRemount;
switch (workInProgress.tag) {
case IndeterminateComponent:
case FunctionComponent:
case SimpleMemoComponent:
workInProgress.type = resolveFunctionForHotReloading(current.type);
break;
case ClassComponent:
workInProgress.type = resolveClassForHotReloading(current.type);
break;
case ForwardRef:
workInProgress.type = resolveForwardRefForHotReloading(current.type);
break;
}
}
return workInProgress;
}
再往上,可以找到是在prepareFreshStack这个方法里执行createWorkInProgress的,到此我们可以知道,babel转换的fiber数据结构里,从root节点,向下遍历执行。
function prepareFreshStack(root, lanes) {
root.finishedWork = null;
root.finishedLanes = NoLanes;
var timeoutHandle = root.timeoutHandle;
if (timeoutHandle !== noTimeout) {
// The root previous suspended and scheduled a timeout to commit a fallback
// state. Now that we have additional work, cancel the timeout.
root.timeoutHandle = noTimeout; // $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above
cancelTimeout(timeoutHandle);
}
if (workInProgress !== null) {
var interruptedWork = workInProgress.return;
while (interruptedWork !== null) {
unwindInterruptedWork(interruptedWork);
interruptedWork = interruptedWork.return;
}
}
workInProgressRoot = root;
workInProgress = createWorkInProgress(root.current, null);
workInProgressRootRenderLanes = subtreeRenderLanes = workInProgressRootIncludedLanes = lanes;
workInProgressRootExitStatus = RootIncomplete;
workInProgressRootFatalError = null;
workInProgressRootSkippedLanes = NoLanes;
workInProgressRootUpdatedLanes = NoLanes;
workInProgressRootPingedLanes = NoLanes;
{
spawnedWorkDuringRender = null;
}
{
ReactStrictModeWarnings.discardPendingWarnings();
}
}
结语
遇到问题,我推荐还是先通过搜索引擎找相关问题是否有人已经做过分析,如果找到3~4篇还是没有解决你的问题的话,不建议再浪费时间,直接找源码debug来看。
如果觉得我写的对你有帮助的话,还请点个赞!!
谢谢!!