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源码debugger-commit阶段的完成

react源码debugger-commit阶段的完成

react源码-debuger解析- createRoot阶段1

react源码debugger-各个hooks的逻辑实现(useState和useEffect)

react源码debugger-各个hooks的逻辑实现(useState和useEffect)