React16源码: React中commitAllHostEffects内部的commitWork的源码实现

commitWork

1 )概述

  • 在 react commit 阶段的 commitRoot 第二个while循环中
  • 调用了 commitAllHostEffects,这个函数不仅仅处理了新增节点
  • 若一个节点已经存在,当它有新的内容要更新或者是它的attributes要更新
  • 这个时候,就需要调用 commitWork

2 )源码

定位到 packages/react-reconciler/src/ReactFiberCommitWork.js#L1033

js 复制代码
function commitWork(current: Fiber | null, finishedWork: Fiber): void {
  if (!supportsMutation) {
    switch (finishedWork.tag) {
      case FunctionComponent:
      case ForwardRef:
      case MemoComponent:
      case SimpleMemoComponent: {
        commitHookEffectList(UnmountMutation, MountMutation, finishedWork);
        return;
      }
    }

    commitContainer(finishedWork);
    return;
  }

  // 首先,根据不同的tag类型来执行不同的操作
  switch (finishedWork.tag) {
    case FunctionComponent:
    case ForwardRef:
    case MemoComponent:
    case SimpleMemoComponent: {
      commitHookEffectList(UnmountMutation, MountMutation, finishedWork);
      return;
    }
    // ClassComponent 不在这里的执行范围内,所以它就return了
    case ClassComponent: {
      return;
    }
    case HostComponent: {
      const instance: Instance = finishedWork.stateNode;
      // // 如果instance不等于null,就代表 instance 对应的节点已经创建了
      if (instance != null) {
        // Commit the work prepared earlier.
        const newProps = finishedWork.memoizedProps;
        // For hydration we reuse the update path but we treat the oldProps
        // as the newProps. The updatePayload will contain the real change in
        // this case.
        const oldProps = current !== null ? current.memoizedProps : newProps;
        const type = finishedWork.type;
        // TODO: Type the updateQueue to be specific to host components.
        // 获取了一个 updatePayload 等于 finishedWork.updateQueue
        // 在 completeWork 的时候,对于 HostComponent 进行了新老 props 的一个对比
        // 这个对比的结果就是返回一个叫做 updatePayload 这么一个数组
        // 这个数组是以2为单位,就是说一个key一个value, 它的第一位是key,第二位是value,然后这样的形式一个一个往后面堆叠
        // 拿到这么一个 updatePayload 之后,我们就知道在commit阶段要去执行怎么样的一个更新操作
        const updatePayload: null | UpdatePayload = (finishedWork.updateQueue: any);
        finishedWork.updateQueue = null;
        // 存在 updatePayload 则执行 commitUpdate
        if (updatePayload !== null) {
          commitUpdate(
            instance,
            updatePayload,
            type,
            oldProps,
            newProps,
            finishedWork,
          );
        }
      }
      return;
    }
    case HostText: {
      invariant(
        finishedWork.stateNode !== null,
        'This should have a text node initialized. This error is likely ' +
          'caused by a bug in React. Please file an issue.',
      );
      const textInstance: TextInstance = finishedWork.stateNode;
      const newText: string = finishedWork.memoizedProps;
      // For hydration we reuse the update path but we treat the oldProps
      // as the newProps. The updatePayload will contain the real change in
      // this case.
      const oldText: string =
        current !== null ? current.memoizedProps : newText;
      commitTextUpdate(textInstance, oldText, newText);
      return;
    }
    case HostRoot: {
      return;
    }
    case Profiler: {
      return;
    }
    case SuspenseComponent: {
      let newState: SuspenseState | null = finishedWork.memoizedState;

      let newDidTimeout;
      let primaryChildParent = finishedWork;
      if (newState === null) {
        newDidTimeout = false;
      } else {
        newDidTimeout = true;
        primaryChildParent = finishedWork.child;
        if (newState.timedOutAt === NoWork) {
          // If the children had not already timed out, record the time.
          // This is used to compute the elapsed time during subsequent
          // attempts to render the children.
          newState.timedOutAt = requestCurrentTime();
        }
      }

      if (primaryChildParent !== null) {
        hideOrUnhideAllChildren(primaryChildParent, newDidTimeout);
      }
      return;
    }
    case IncompleteClassComponent: {
      return;
    }
    default: {
      invariant(
        false,
        'This unit of work tag should not have side-effects. This error is ' +
          'likely caused by a bug in React. Please file an issue.',
      );
    }
  }
}
  • 进入 commitUpdate

    js 复制代码
    // packages/react-dom/src/client/ReactDOMHostConfig.js#L324
    export function commitUpdate(
      domElement: Instance,
      updatePayload: Array<mixed>,
      type: string,
      oldProps: Props,
      newProps: Props,
      internalInstanceHandle: Object,
    ): void {
      // Update the props handle so that we know which props are the ones with
      // with current event handlers.
      updateFiberProps(domElement, newProps); // 将新的props挂载到新的属性上
      // Apply the diff to the DOM node.
      updateProperties(domElement, updatePayload, type, oldProps, newProps);
    }
    
    // packages/react-dom/src/client/ReactDOMComponentTree.js#L85
    export function updateFiberProps(node, props) {
      node[internalEventHandlersKey] = props;
    }
    
    // packages/react-dom/src/client/ReactDOMComponent.js#L775
    // Apply the diff.
    export function updateProperties(
      domElement: Element,
      updatePayload: Array<any>,
      tag: string,
      lastRawProps: Object,
      nextRawProps: Object,
    ): void {
      // Update checked *before* name.
      // In the middle of an update, it is possible to have multiple checked.
      // When a checked radio tries to change name, browser makes another radio's checked false.
      // 像是 radio 可以设置 value 绑定来设置
      // 对于radio标签,设置通过 value 的绑定来进行是否选中的控制, 相当于是要把value转化成哪一个radio标签,它的checked属性是等于true的这么一种情况
      // 这是为什么当初在执行 diff property 的时候,判断如果这个标签是input,它的 updatePayload 直接等于了一个空数组
      // 也就是说,即便我们后续 updatePayload 的里面没有放任何的内容,但它返回的仍然是一个空数组,仍然要执行到我们这个 updateProperties 方法里面
      // 因为最终我们要去根据一些不同的情况来更新,像radio标签,checkbox 标签 的一个 checked 相关的一些状态
      if (
        tag === 'input' &&
        nextRawProps.type === 'radio' &&
        nextRawProps.name != null
      ) {
        ReactDOMInput.updateChecked(domElement, nextRawProps);
      }
    
      const wasCustomComponentTag = isCustomComponent(tag, lastRawProps);
      const isCustomComponentTag = isCustomComponent(tag, nextRawProps);
      // Apply the diff.
      // 更新 dom 节点的 properties
      updateDOMProperties(
        domElement,
        updatePayload,
        wasCustomComponentTag,
        isCustomComponentTag,
      );
    
      // TODO: Ensure that an update gets scheduled if any of the special props
      // changed.
      // 表单标签的处理
      // 其实就是把这些 input, textarea, select 标签, 特殊的一些属性,一些 attribute 给它进行一个更新的一个过程
      switch (tag) {
        case 'input':
          // Update the wrapper around inputs *after* updating props. This has to
          // happen after `updateDOMProperties`. Otherwise HTML5 input validations
          // raise warnings and prevent the new value from being assigned.
          ReactDOMInput.updateWrapper(domElement, nextRawProps);
          break;
        case 'textarea':
          ReactDOMTextarea.updateWrapper(domElement, nextRawProps);
          break;
        case 'select':
          // <select> value update needs to occur after <option> children
          // reconciliation
          ReactDOMSelect.postUpdateWrapper(domElement, nextRawProps);
          break;
      }
    }
    
    // packages/react-dom/src/client/ReactDOMInput.js#L128
    export function updateChecked(element: Element, props: Object) {
      const node = ((element: any): InputWithWrapperState);
      const checked = props.checked;
      if (checked != null) {
        // dom操作
        DOMPropertyOperations.setValueForProperty(node, 'checked', checked, false);
      }
    }
    
    // packages/react-dom/src/shared/isCustomComponent.js#L10
    function isCustomComponent(tagName: string, props: Object) {
      // 它根据标签名字上,是否有 - 的存在, 并且 props.is 是 string 则是
      if (tagName.indexOf('-') === -1) {
        return typeof props.is === 'string';
      }
      // 有 - 的,屏蔽自有的一些,排除之后其他都算 自定义
      switch (tagName) {
        // These are reserved SVG and MathML elements.
        // We don't mind this whitelist too much because we expect it to never grow.
        // The alternative is to track the namespace in a few places which is convoluted.
        // https://w3c.github.io/webcomponents/spec/custom/#custom-elements-core-concepts
        case 'annotation-xml':
        case 'color-profile':
        case 'font-face':
        case 'font-face-src':
        case 'font-face-uri':
        case 'font-face-format':
        case 'font-face-name':
        case 'missing-glyph':
          return false;
        default:
          return true;
      }
    }
    
    // packages/react-dom/src/client/ReactDOMComponent.js#L326
    function updateDOMProperties(
      domElement: Element,
      updatePayload: Array<any>,
      wasCustomComponentTag: boolean,
      isCustomComponentTag: boolean,
    ): void {
      // TODO: Handle wasCustomComponentTag
      // 遍历这个updatepaylow的这个数组,然后记住这边的下标增加是每次加2,而不是加1了
      for (let i = 0; i < updatePayload.length; i += 2) {
        // i 对应的就是 propKey, 然后i加1对应的就是 propValue
        const propKey = updatePayload[i];
        const propValue = updatePayload[i + 1];
        // 如果是style,会对每一个style的属性来执行
        if (propKey === STYLE) {
          // 里面会遍历这个style对象里面的每一个style属性,然后去设置对应的值
          // 我们拿到一个对象,最终如何给它转换成真正的CSS属性给它设置上去的一个过程
          // 其中还要考虑一些属性是删除的操作
          CSSPropertyOperations.setValueForStyles(domElement, propValue);
        } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
          setInnerHTML(domElement, propValue);
        } else if (propKey === CHILDREN) {
          setTextContent(domElement, propValue);
        } else {
          // 对于其他属性的处理
          DOMPropertyOperations.setValueForProperty(
            domElement,
            propKey,
            propValue,
            isCustomComponentTag,
          );
        }
      }
    }
    // packages/react-dom/src/client/ReactDOMHostConfig.js#L343
    export function commitTextUpdate(
      textInstance: TextInstance,
      oldText: string,
      newText: string,
    ): void {
      textInstance.nodeValue = newText;
    }
  • 对于 HostComponent 来说

    • 因为更新的过程,有一大部分是放在 completeUnitOfWork 里面去做了
    • completeWork 的时候会获得这个 updatePayload
    • 这个 payload 已经告诉我们具体要做哪些属性的操作了
    • 它里面也包含了我们有一些 attribute 要删除的情况
    • 就是说它的 value 为 null 的情况, 标示着这个属性是要被删除的
    • 所以, 那一部分工作做完了之后,到这里其实已经比较的简单了
    • 只需要根据 updatePayload 里面相关的内容进行一个整体的更新就可以了
  • 对于 HostText 它也要进行一个更新,它调用了一个 commitTextUpdate

  • 以上就是整个 commitWork 里面做的事情, 具体的细节写在代码注释里

相关推荐
sunbyte38 分钟前
Tailwind CSS v4 主题化实践入门(自定义 Theme + 主题模式切换)✨
前端·javascript·css·tailwindcss
湛海不过深蓝2 小时前
【css】css统一设置变量
前端·css
程序员的世界你不懂2 小时前
tomcat6性能优化
前端·性能优化·firefox
爱吃巧克力的程序媛2 小时前
QML ProgressBar控件详解
前端
进取星辰2 小时前
21、魔法传送阵——React 19 文件上传优化
前端·react.js·前端框架
wqqqianqian2 小时前
国产linux系统(银河麒麟,统信uos)使用 PageOffice 在线打开Word文件,并用前端对话框实现填空填表
linux·前端·word·pageoffice
BillKu2 小时前
CSS实现图片垂直居中方法
前端·javascript·css
GISer_Jing3 小时前
前端性能优化全攻略:从基础体验到首屏加载的深度实践
前端·javascript·性能优化
pink大呲花3 小时前
深入理解 Vue 全局导航守卫:分类、作用与参数详解
前端·javascript·vue.js
xixixiLucky3 小时前
配置Java Selenium Web自动化测试环境
java·前端·selenium