react源码解析-debugger阶段-函数组件App的beginWork阶段
Posted coderlin_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了react源码解析-debugger阶段-函数组件App的beginWork阶段相关的知识,希望对你有一定的参考价值。
各种类型fiber的beginWork
函数组件的beginWork
接第一章,我们走完了createRoot到rootFiber执行完beginWork阶段,workInprogress.child也就是App函数的fiber被创建完毕之后的阶段。
- rootFiber执行完递阶段之后,返回了App fiber,将App fiber赋值给workINprogress.
- 当performUnitOfWork执行完毕之后,当前浏览器无多余时间,等待下次调度执行performConcurrentWorkOnRoot
- 然后执行performUnitOfWork,去执行App fiber的beginwork。
这章继续了解各种类型fiber的beginWork。
对于App fiber,当他执行beginwork的时候,是没有alterNate的,所以处于mount阶段。而且App是一个函数组件,而react事先不知道他是普通函数还是函数组件,所以App fiber的tag是2也就是
export const ClassComponent = 1; // 类组件
export const IndeterminateComponent = 2; // 初始化的时候不知道是函数组件还是类组件
IndeterminateComponent的beginWork
当App fiber进入beginWork的时候,
直接执行mountIndeterminateComponent函数。
这里需要注意一点,对于函数组建的fiber,长如下这样
函数组建的vdom
$$typeof: Symbol(react.element)
type: ƒ App() //函数
key: null,
ref: null,
props:
类组件的vdom
$$typeof: Symbol(react.element)
key: null
props:
ref: null
type: ƒ DD() //类
原生的vdom
$$typeof: Symbol(react.element)
type: "div", // type是标签
key: null,
ref: null,
props:
"children": "123123"
,
字符串数字不是vdom
// 函数组建的fiber
const fiber =
alternate: null,
elementType: ƒ App(),
type: ƒ App(),
updateQueue: null,
tag: 2,
return: parentFiber,
sibling: siblingFiber,
.....
所以fiber.type就是指App函数本身。
mountIndeterminateComponent
// 对于第一次执行的函数组件,react不知道他是函数组件还是普通函数,统统转为indeterminateComponent,即不清楚的component
function mountIndeterminateComponent(
_current,
workInProgress,
Component,
renderLanes,
)...
value = renderWithHooks(
null,
workInProgress,
Component,
props,
context,
renderLanes,
);
。。。。
if( !disableModulePatternComponents &&
typeof value === 'object' &&
value !== null &&
typeof value.render === 'function' &&
value.$$typeof === undefined)
...
workInProgress.tag = ClassComponent; //判断为类组件
initializeUpdateQueue(workInProgress); //初始化updateQueue
adoptClassInstance(workInProgress, value);
mountClassInstance(workInProgress, Component, props, renderLanes); //实例化
return finishClassComponent( //返回render内容。
null,
workInProgress,
Component,
true,
hasContext,
renderLanes,
);
else
// Proceed under the assumption that this is a function component
workInProgress.tag = FunctionComponent; //标识为函数组件
reconcileChildren(null, workInProgress, value, renderLanes); //调度儿子生成fiber
return workInProgress.child;
..
这个renderWithHooks也很重要,他的作用就是来执行函数,获取函数组件返回的vdom。
renderWithHooks
function renderWithHooks<Props, SecondArg>(
current: Fiber | null, //alternate
workInProgress: Fiber, // App
Component: (p: Props, arg: SecondArg) => any, // APp函数
props: Props, // props
secondArg: SecondArg,
nextRenderLanes: Lanes,
)
ReactCurrentDispatcher.current = // 赋值React hooks
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
// 执行函数组件
let children = Component(props, secondArg);
// 执行完函数组件后,赋值hooks为ContextOnlyDispatcher,那么函数组件执行后调用比如useState的时候就会报错。
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
return children; // 函数组件返回的vdom
- 简化后的renderWithHooks就做了上述这些事情,函数组件执行的时候,可能会有一些hooks,比如useState,当函数组件执行之前,会通过当前是mount还是update,赋值HooksDispatcherOnMount或者是HooksDispatcherOnUpdate。
- 然后执行函数,获取return的vdom。
- 然后继续赋值hooks为ContextOnlyDispatcher(用来抛错的),这样如果有hooks在函数组件执行完后再执行Hooks的话,就会报错。因为此时hooks被赋值了ContextOnlyDispatcher
执行完renderWithHooks后,可以看到,他会根据函数执行返回的value判断当前FIber是类组件还是函数组件,因为rooFIber的儿子也就是App有可能是一个类。这里我们看函数组件的逻辑。
这里有一个点,所有的函数组件在Mount的时候,都会标识为IndeterminateComponent,等到执行完renderWithHooks的时候,才能确定是函数组件,才会打上函数组建的标识。在Update的时候就不会走mountIndeterminateComponent的逻辑了。
执行完renderWithHooks后,标识当前Appfiber为函数组件,然后执行
reconcileChildren(null, workInProgress, value, renderLanes);
function reconcileChildren(
current: Fiber | null,
workInProgress: Fiber,
nextChildren: any, //即将处理的vdom
renderLanes: Lanes,
)
第三个参数是value,也就是将要处理的子节点,会根据他生成fiber
此时Appfiber是没有alternate的,所以走mountChildFibers逻辑。
他的逻辑跟rootFiber调用reconcileChildren差不多,只不过这一次,是mountCHildFibers,
export const reconcileChildFibers = ChildReconciler(true);
export const mountChildFibers = ChildReconciler(false);
ChildReconciler返回reconcileChildFibers。所以mountChildFibers和reconcileChildFibers都是调用reconcileChildFibers。
reconcileChildFibers
reconcileSingleElement会根据vdom创建fiber,并且将刚创建的fiber跟workInprogress连接起来。这里如果有其它类型的vdom
还可能出现多个儿子的。多个儿子会全部创建fiber,然后通过return 和sibling指针关联,然后返回大儿子,后续会详细看。
还有是字符串或者数字的。会单独处理,不会生成fiber。做优化使用
而plceSingleChild会判断是否需要加上flags
因为当前是mount,所以传入的是false,并且APp fiber没有alternate,所以,不会加上flags。这也验证了,第一次mount的时候,只有rooFiber的儿子App fiber会打上Plcament标记。
mountIndeterminationComponent执行完reconcileChildren后,返回了workInprogress.child。也就是
我们的DD类组件的fiber,简称DDfiber。
- 然后继续走之前rootFiber的逻辑,因为App fiber的beginWork返回了DD fiber,不会进入归阶段,将DD fiber赋值给workInprogress之后
- 继续while循环,判断是否有多余的时间,没有的话等schedule调度我们注册的performConcurrenWorkOnRoot,
- performConcurrenWorkOnRoot最终又会调用performUnitOfWork,执行workInprogress的beginWork,只不过这次workInprogress换成了DDfiber。
- 自此,函数组件App fiber的beginWork阶段就到此结束,轮到DD fiber的beginWork了。
- 总结:函数组件在被创建fiber的时候,tag会被标识为indeterminationComponent,即不确定的组件,因为react第一次无法确认她是否是真的函数组件,
- 然后只能执行mountIndeterminationComponent方法,去执行renderWithHooks,这个方法会赋值hooks,执行函数,将函数的返回值返回,然后根据函数的返回值判断函数的类型,这时候才会将Fiber.tag标识为FunctionComponent,
- 然后创建子fiber连接起来,将子fiber返回,完成Appfiber的beginWork阶段。
类组件的beginWork。
Appfiber执行完递阶段之后,轮到DDfiber执行递阶段
…
以上是关于react源码解析-debugger阶段-函数组件App的beginWork阶段的主要内容,如果未能解决你的问题,请参考以下文章
react源码debugger-各种fiber的completeWork阶段
react源码-debuger解析- createRoot阶段1