我的编程空间,编程开发者的网络收藏夹
学习永远不晚

React Fiber构建completeWork源码解析

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

React Fiber构建completeWork源码解析

引言

之前我们介绍了beginWork,react使用的是深度优先遍历算法,整个fiber的构建都遵循此算法。

这也意味着,并不是所有节点beginWork完成后,才去进行completeWork。

当beginWork的next为null时,将进去completeWork。

一. completeUnitOfWork

function completeUnitOfWork(unitOfWork) {
  var completedWork = unitOfWork;
  do {
     // ...
     next = completeWork(current, completedWork, subtreeRenderLanes);
     // ...
     if (returnFiber !== null && (returnFiber.flags & Incomplete) === NoFlags) {
       if (returnFiber.firstEffect === null) {
          returnFiber.firstEffect = completedWork.firstEffect;
        }
        if (completedWork.lastEffect !== null) {
          if (returnFiber.lastEffect !== null) {
              returnFiber.lastEffect.nextEffect = completedWork.firstEffect;
            }
            returnFiber.lastEffect = completedWork.lastEffect;
        }
        var flags = completedWork.flags;
        if (flags > PerformedWork) {
          if (returnFiber.lastEffect !== null) {
            returnFiber.lastEffect.nextEffect = completedWork;
          }else {
            returnFiber.firstEffect = completedWork;
          }
          returnFiber.lastEffect = completedWork;
        }
     }
     var siblingFiber = completedWork.sibling;
     if (siblingFiber !== null) {
        workInProgress = siblingFiber;
        return;
     }
     completedWork = returnFiber;
     workInProgress = completedWork;
  } while (completedWork !== null);
  // ...
}

根据深度优先算法,当beginWork完成其中某个子树干的最后一个节点时,进入completeUnitOfWork。根据最后的这个节点先完成completeWork,依次往上,直到找到相邻节点。

核心方法分为2部分,其一:completeWork,其二:完成effect挂载,并串联所有节点的effect(包括节点内部的effect)组装成环状链表。

二. completeWork

function completeWork(current, workInProgress, renderLanes) {
    var newProps = workInProgress.pendingProps;
    switch (workInProgress.tag) {
      case IndeterminateComponent:
      case LazyComponent:
      case SimpleMemoComponent:
      case FunctionComponent:
      case ForwardRef:
      case Fragment:
      case Mode:
      case Profiler:
      case ContextConsumer:
      case MemoComponent:
        return null;
      case ClassComponent:
        // ...
      case HostRoot:
       // ...
        updateHostContainer(workInProgress);
        return null;
      case HostComponent:
        // ...
         var instance = createInstance(type, newProps, rootContainerInstance, currentHostContext, workInProgress);
         appendAllChildren(instance, workInProgress, false, false);
         workInProgress.stateNode = instance;
        // ...
     // ...
}

这里很神奇的是,在react内部updateHostContainer居然是个空函数,也许后续版本做do something吧。 普通节点将进入HostComponent。

createInstance

function createInstance(type, props, rootContainerInstance, hostContext, internalInstanceHandle) {
    var parentNamespace;
    {
      // TODO: take namespace into account when validating.
      var hostContextDev = hostContext;
      validateDOMNesting(type, null, hostContextDev.ancestorInfo);
      if (typeof props.children === 'string' || typeof props.children === 'number') {
        var string = '' + props.children;
        var ownAncestorInfo = updatedAncestorInfo(hostContextDev.ancestorInfo, type);
        validateDOMNesting(null, string, ownAncestorInfo);
      }
      parentNamespace = hostContextDev.namespace;
    }
    var domElement = createElement(type, props, rootContainerInstance, parentNamespace);
    precacheFiberNode(internalInstanceHandle, domElement);
    updateFiberProps(domElement, props);
    return domElement;
  }

createElement

function createElement(type, props, rootContainerElement, parentNamespace) {
  // script标签处理...
  // ...
  // flow相关webComponents处理
  // ...
  domElement = ownerDocument.createElement(type);
  // select标签特殊处理,单独设置multiple以及size
  // 非法标签告警处理...
  return domElement;
}

到这里,我们可以看到开始创建每个fiber节点对应的dom对象了。但是并没有插入到文档流中。 那么真实的dom是如何连接到fiber对象呢?

precacheFiberNode,会在每个真实dom对象下,挂载对应的节点的fiber,precache是以_reactFiber$+随机数的属性。

updateFiberProps,会在每个真实dom对象下,挂载对应的props children即element对象,以_reactProps$+随机数。

问题来了:每个fiber对应的真实dom对象,是单个构建单个存储?还是构建一个总的dom树?这之间是如何关联起来的?

appendAllChildren

appendAllChildren = function (parent, workInProgress, needsVisibilityToggle, isHidden) {
    var node = workInProgress.child;
      while (node !== null) {
        if (node.tag === HostComponent || node.tag === HostText) {
          appendInitialChild(parent, node.stateNode);
        } else if (node.tag === HostPortal) ; else if (node.child !== null) {
          node.child.return = node;
          node = node.child;
          continue;
        }
        if (node === workInProgress) {
          return;
        }
        while (node.sibling === null) {
          if (node.return === null || node.return === workInProgress) {
            return;
          }
          node = node.return;
        }
        node.sibling.return = node.return;
        node = node.sibling;
      }
}

这里已经很明显了,根据之前创建的fiber链表,循环node节点,普通节点将调用appendInitialChild。即使用:parentInstance.appendChild(child); 至此可以看到循环结束后,生成了整个待插入的DOM节点(页面首次渲染时)。

另外需要注意的是,根据fiber关联dom也是在这个阶段进行的(不是dom关联fiber)

最后如何存在ref,当前的fiber树对应的flags将和Ref的二进制数据取位运算或。(这很重要)

三. Effect

react推崇的是函数式编程,在一个函数组件里,如果存在useEffect等方法,那么react认为这是一个副作用函数组件。那么这些副作用是如何组织起来,又是在什么阶段运行的呢?

早在beginWork,fiber的flags默认都是二进制0,如果存在副作用,如:useEffect,ref,useLayoutEffect等等,首次将被设置为Placement。但为什么存在useEffect函数组件的fiber对象,flags都是256以上的数值?

我们以useEffect为例,一探究竟。

useEffect

function useEffect(create, deps) {
    var dispatcher = resolveDispatcher();
    return dispatcher.useEffect(create, deps);
}

没错,useEffect方法定义就两行代码。

resolveDispatcher

function resolveDispatcher() {
    var dispatcher = ReactCurrentDispatcher.current;
    if (!(dispatcher !== null)) {
      {
        throw Error( "Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:\n1. You might have mismatching versions of React and the renderer (such as React DOM)\n2. You might be breaking the Rules of Hooks\n3. You might have more than one copy of React in the same app\nSee https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem." );
      }
    }
    return dispatcher;
  }

我们继续看下ReactCurrentDispatcher的定义:

const ReactCurrentDispatcher = {
  
  current: (null: null | Dispatcher),
};

最早的ReactCurrentDispatcher,是在renderRoot阶段。如果root或Lane被改变了,原来的dispatch可能被置空了或首次不存在,使用当前的ContextOnlyDispatcher替代。

在函数组件beginWork阶段,在执行函数组件生成element对象之前,会赋值HooksDispatcherOnMount,这就是dispatch。

我们来看看HooksDispatcher:

{
      readContext: function (context, observedBits) {
        return readContext(context, observedBits);
      },
      useCallback: function (callback, deps) {
        currentHookNameInDev = 'useCallback';
        mountHookTypesDev();
        checkDepsAreArrayDev(deps);
        return mountCallback(callback, deps);
      },
      useContext: function (context, observedBits) {
        currentHookNameInDev = 'useContext';
        mountHookTypesDev();
        return readContext(context, observedBits);
      },
      useEffect: function (create, deps) {
        currentHookNameInDev = 'useEffect';
        mountHookTypesDev();
        checkDepsAreArrayDev(deps);
        return mountEffect(create, deps);
      },
      useImperativeHandle: function (ref, create, deps) {
        currentHookNameInDev = 'useImperativeHandle';
        mountHookTypesDev();
        checkDepsAreArrayDev(deps);
        return mountImperativeHandle(ref, create, deps);
      },
      useLayoutEffect: function (create, deps) {
        currentHookNameInDev = 'useLayoutEffect';
        mountHookTypesDev();
        checkDepsAreArrayDev(deps);
        return mountLayoutEffect(create, deps);
      },
      useMemo: function (create, deps) {
        currentHookNameInDev = 'useMemo';
        mountHookTypesDev();
        checkDepsAreArrayDev(deps);
        var prevDispatcher = ReactCurrentDispatcher$1.current;
        ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnMountInDEV;
        try {
          return mountMemo(create, deps);
        } finally {
          ReactCurrentDispatcher$1.current = prevDispatcher;
        }
      },
      useReducer: function (reducer, initialArg, init) {
        currentHookNameInDev = 'useReducer';
        mountHookTypesDev();
        var prevDispatcher = ReactCurrentDispatcher$1.current;
        ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnMountInDEV;
        try {
          return mountReducer(reducer, initialArg, init);
        } finally {
          ReactCurrentDispatcher$1.current = prevDispatcher;
        }
      },
      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;
        }
      },
      useDebugValue: function (value, formatterFn) {
        currentHookNameInDev = 'useDebugValue';
        mountHookTypesDev();
        return mountDebugValue();
      },
      useDeferredValue: function (value) {
        currentHookNameInDev = 'useDeferredValue';
        mountHookTypesDev();
        return mountDeferredValue(value);
      },
      useTransition: function () {
        currentHookNameInDev = 'useTransition';
        mountHookTypesDev();
        return mountTransition();
      },
      useMutableSource: function (source, getSnapshot, subscribe) {
        currentHookNameInDev = 'useMutableSource';
        mountHookTypesDev();
        return mountMutableSource(source, getSnapshot, subscribe);
      },
      useOpaqueIdentifier: function () {
        currentHookNameInDev = 'useOpaqueIdentifier';
        mountHookTypesDev();
        return mountOpaqueIdentifier();
      },
      unstable_isNewReconciler: enableNewReconciler
    };

其中useEffect重点执行mountEffect(create, deps)

function mountEffect(create, deps) {
    {
      // $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
      if ('undefined' !== typeof jest) {
        warnIfNotCurrentlyActingEffectsInDEV(currentlyRenderingFiber$1);
      }
    }
    return mountEffectImpl(Update | Passive, Passive$1, create, deps);
  }

mountEffectImpl

function mountEffectImpl(fiberFlags, hookFlags, create, deps) {
  var hook = mountWorkInProgressHook();
    var nextDeps = deps === undefined ? null : deps;
    currentlyRenderingFiber$1.flags |= fiberFlags;
    hook.memoizedState = pushEffect(HasEffect | hookFlags, create, undefined, nextDeps);
}

hook对象数据结构如下:

{
    memoizedState: null,
    baseState: null,
    baseQueue: null,
    queue: null,
    next: null
};

问题来了:

  • currentlyRenderingFiber是什么?和workInProgressFiber有什么关系?
  • 多个函数组件和单个函数组件中多个Hook是如何关联起来的?
  • 整个hook和fiber怎么关联起来的?
  • 完整的hooks数据结构又是什么?

currentlyRenderingFiber是当前正在rendering阶段的fiber对象,早在renderHook初始化阶段赋值了workInProgressFiber。所以当前函数组件的flags在这里被改变了,即有副作用的函数flags = flags | Update | Passive。

根据二进制位运算,根函数组件库flags = 518,当然这个数值也不是固定不变的,因为变化的beginWork阶段初始flags值。是根据不同的effects会有不同的初始值。

pushEffect

function pushEffect(tag, create, destroy, deps) {
    var effect = {
      tag: tag,
      create: create,
      destroy: destroy,
      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;
  }

对于tag的值,是HasEffect和Passive按位运算或的结果,实际上固定是5。

需要注意的是,函数组件的updateQueue和rootFiber的不一样,以及普通节点的数据结构和作用也都不一样。 函数组件的updateQueue关联的是effect。这和render初始化阶段rootFiber有巨大的差异。

上面的代码很简单,每个函数组件如果存在多个effect,那么会将这些effect顺序关联起来,这个函数组件的fiberr.updateQueue对应lastEffect,next即下一个effect,直到最后形成一个首尾相连的环状链表结构。

为什么是环状?这个待到后续调度阶段再解释。

再思考一个问题:这里只是解决了单个组件内的effect构建,那么整个fiber链表里effect构建是怎么样的?执行的顺序又是什么?

四. rootFiber-Effect

在completedWork的最后,根据深度优先遍历算法,将每个节点的firstEffect层层往上传递,一直到rootFiber。而lastEffect也是层层往上判断,直到上层最后一个effect,做为rootFiber的lastEffect。

每个fiber effect通过nextEffect链接起来,而fiber内部通过updateQueue链接自身的effect环状链表。

至此,completeWork阶段就完成了,rootFiber以及各fiber节点大部分属性都构建完成了。

下一章,将进入commit阶段,更多关于React Fiber构建completeWork的资料请关注编程网其它相关文章!

以上就是React Fiber构建completeWork源码解析的详细内容,更多关于React Fiber构建completeWork的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

React Fiber构建completeWork源码解析

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

React Fiber构建completeWork源码解析

这篇文章主要为大家介绍了React Fiber构建completeWork源码解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-02-06

ReactFiber构建源码解析

这篇文章主要为大家介绍了ReactFiber构建源码解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-02-06

ReactFiber构建beginWork源码解析

这篇文章主要为大家介绍了ReactFiber构建beginWork源码解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-02-06

TensorFlow源代码构建流程记录解析

这篇文章主要为大家介绍了TensorFlow源代码构建流程记录解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-01-03

React SSR架构Streaming Render与Selective Hydration源码分析

本文小编为大家详细介绍“React SSR架构Streaming Render与Selective Hydration源码分析”,内容详细,步骤清晰,细节处理妥当,希望这篇“React SSR架构Streaming Render与Selec
2023-07-05

React深入分析更新的创建源码

React组件分为函数组件与class组件;函数组件是无状态组件,class称为类组件;函数组件只有props,没有自己的私有数据和生命周期函数;class组件有自己私有数据(this.state)和生命周期函数
2023-01-14

react源码合成事件深入解析

这篇文章主要为大家介绍了react源码合成事件深入解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-01-06

go语言构建顺序源码分析

这篇文章主要介绍“go语言构建顺序源码分析”,在日常操作中,相信很多人在go语言构建顺序源码分析问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”go语言构建顺序源码分析”的疑惑有所帮助!接下来,请跟着小编一起来
2023-07-05

React SSR架构Stream Rendering与Suspense for Data Fetching源码分析

这篇“React SSR架构Stream Rendering与Suspense for Data Fetching源码分析”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完
2023-07-05

ConcurrentHashMap 存储结构源码解析

这篇文章主要为大家介绍了ConcurrentHashMap 存储结构源码解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-11-13

react源码层深入刨析babel解析jsx实现

同作为MVVM框架,React相比于Vue来讲,上手更需要JavaScript功底深厚一些,本系列将阅读React相关源码,从jsx->VDom->RDOM等一些列的过程,将会在本系列中一一讲解
2022-11-13

React Refs 的使用forwardRef 源码示例解析

这篇文章主要为大家介绍了React 之 Refs 的使用和 forwardRef 的源码解读,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-11-13

从源码构建docker-ce的过程分析

这篇文章主要介绍了从源码构建docker-ce的过程,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
2022-12-20

微前端架构ModuleFederationPlugin源码解析

这篇文章主要为大家介绍了微前端架构ModuleFederationPlugin源码解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-11-13

React源码分析之useCallback与useMemo及useContext详解

这篇文章主要介绍了ReactuseCallback与useMemo及useContext源码分析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
2022-11-13

编程热搜

目录