开篇
好了,前面我们已经分析了主流程上fiber的构建和调度,这时候我们就可以去接下来关注更新了,题目谈到了支线,为什么这么说,因为当我们走到beginWork时组件更新和构建有特别多的分支在beginWork中,我们只关注一些主流的分支,就先讲讲我们最常用的函数式组件的更新和构建。源码地址
switch (workInProgress.tag) {
// 忽略代码
// 函数式组件的子节点构建
case FunctionComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateFunctionComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
// 忽略代码
更新分析
我们跟着源码beginWork的逻辑走,beginWork探寻阶段在进入的时候,就主要会做一件事件,设置是否需要更新的状态,而这个状态是我们后面许多代码判断的基础。
if (current !== null) {
// 双缓存机制
const oldProps = current.memoizedProps;
const newProps = workInProgress.pendingProps;
// 不相等就进入对比
if (
oldProps !== newProps ||
hasLegacyContextChanged() ||
(__DEV__ ? workInProgress.type !== current.type : false)
) {
// 状态标记需要更新这个状态,记住这个状态后续很多工作都与之相关
didReceiveUpdate = true;
// 如果渲染车道内不包含更新就不需要更新
} else if (!includesSomeLane(renderLanes, updateLanes)) {
didReceiveUpdate = false;
// 上下文特殊处理,优化
switch (workInProgress.tag) {}
// 循坏子节点需要更新不
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
} else {
if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
// 处理Suspense组件不会Context更新时更新的问题强制将标记变为true。
didReceiveUpdate = true;
} else {
// An update was scheduled on this fiber, but there are no new props
// nor legacy context. Set this to false. If an update queue or context
// consumer produces a changed value, it will set this to true. Otherwise,
// the component will assume the children have not changed and bail out.
didReceiveUpdate = false;
}
}
} else {
// 剩下的就是都不需要更新
didReceiveUpdate = false;
}
可以看到上面做的许多事情都是为了设置一个状态,之后剩下的逻辑就走到了上一张图,也就是我们可以知道一件事情,就是实际上更新和初次构建大多数情况下是复用一套的。再讲下一步updateFunctionComponent前我们先回顾一下前面的知识也就是beginwork是去进行一个reactElement子节点的构建和fiber子节点构建并返回fiber子节点,也就是updateXXX基本都会做的事情,这里的updateFunctionComponent也做了这样的操作。
function updateFunctionComponent(
current,
workInProgress,
Component,
nextProps: any,
renderLanes,
) {
// 忽略代码
let context;
let nextChildren;
// 这里和useContext有关,hooks分支中我们去讲一下
prepareToReadContext(workInProgress, renderLanes);
if (__DEV__) {
//忽略代码
} else {
// 这里就直接进入了hooks相关逻辑,最后返回下级ReactElement对象
nextChildren = renderWithHooks(
current,
workInProgress,
Component,
nextProps,
context,
renderLanes,
);
}
// 老规矩,如果didReceiveUpdate这个状态也就是前面提到的不需要更新,向下查找子节
if (current !== null && !didReceiveUpdate) {
bailoutHooks(current, workInProgress, renderLanes);
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
// React DevTools reads this flag.我们暂时都略过performance的东西
workInProgress.flags |= PerformedWork;
// 向下生成子fiber节点
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
hook分支
当我们看到这的时候,就已经不知不觉要讲到hooks,我先谈谈的我的思考和理解把:其实去对比了一下其他组件,函数式组件应该算是比较特殊的一种组件,在这里我犹豫了很久要不要把hooks单独拉出去再写一篇文章因为这一块确实用个成语说是浓墨重彩,很多的面试题以及我们的使用都是围绕hooks,但我觉得不!!!我觉得我应该把它放平,它本质仅仅只是一个为了控制fiber节点以及副作用提交时提供的一套维护机制或者说控制机制模块(我这抽象概念是真不行啊),它本来也就只是属于整个fiber构建流程的一个部分或者分支而已。这里先把一些数据结构和变量的东西放在fiber的强相关总结中,可以对照着看有助于理解接下来的部分(其实我也是一边看总结的变量和数据结构一边分析源码,变量太多了记不住),当然我们的视角还是以fiber为主。
好我们步入正题,进入到hooks的开始renderWithHooks
export function renderWithHooks<Props, SecondArg>(
current: Fiber | null,
workInProgress: Fiber,
Component: (p: Props, arg: SecondArg) => any,
props: Props,
secondArg: SecondArg,
nextRenderLanes: Lanes,
): any {
// 1阶段:去设置全局的状态
renderLanes = nextRenderLanes; // 当前渲染优先级
currentlyRenderingFiber = workInProgress; // 当前fiber节点, 也就是function组件对应的fiber节点
if (__DEV__) {
//忽略代码
}
// 清除当前fiber的遗留状态
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
workInProgress.lanes = NoLanes;
if (__DEV__) {
//忽略代码
} else {
// 2阶段:生成ReactElement子节点
// ReactCurrentDispatcher来自react-dom中的全局变量,确认是初始化还是更新
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
// 执行function函数
let children = Component(props, secondArg);
}
// 重置全局变量,并返回
// 执行function之后, 还原被修改的全局变量, 不影响下一次调用
renderLanes = NoLanes;
currentlyRenderingFiber = (null: any);
currentHook = null;
workInProgressHook = null;
didScheduleRenderPhaseUpdate = false;
return children;
}
这里就是分为了3个阶段:
第一个阶段:设置全局变量,让currentlyRenderingFiber的指针暂时指向于当前任务,然后清除那里我们可以思考一下,我的答案是如果后面在其实地方给例如:useState中的updateState这些属性设置了值,那么我们就需要在下一次更新或者构建的时候先清除这些状态,再去重新赋值。
第二个阶段:确定了到底是调用更新方法还是初始化方法,然后这里的Component函数实际上就是,我们在写代码里的函数式组件,然后这个可以提一下(在前面的传参中type,实际上是在react-dom中,把函数存储在type属性上,这仅仅只是function, class, ForwardRef的特殊处理)。
第三个阶段:重置变量。
执行过程:在线尝试
打了一下打断点,简单说一下整个的调用过程,首先当我们的组件初始化进入到beginWork的时候,对于第一次执行的函数组件,react不知道他是函数组件还是普通函数,统统转为indeterminateComponent,即不清楚的component,然后这里会执行renderWithHooks,执行完这之后才会打上Fucntion的标记然后在我们Update更新的时候就不会走indeterminateComponent的逻辑了,而是会走到updateFunctionComponent的逻辑。
总结
这一篇就暂时先只讲到调用Component也就是调用函数,而下一篇会讲到进入Component函数中具体API的实现,以及是如何控制fiber的,以及算法。