React16源码: React中处理hydrate的核心流程源码实现

从源码角度看React-Hydrate原理 React 渲染过程,即render阶段以及commit阶段。渲染过程和差不多,两者之间最大的区别就是,在render阶段,会尝试复用(hydrate)浏览器现有的 dom 节点,并相互关联 dom 实例和 fiber,以及找出 dom 属性和 fiber 属性之间的差异。 阅读详情

hydrate


1 )概述

  • hydrate 在react当中不算特别重要, 但是很多时候会用到的一个API
  • 这个 API 它主要作用就是在进入第一次渲染的时候,如果本身 dom 树上面已经有一个dom结构存在
  • 是否可以去利用这一部分已经存在的dom,然后去避免掉在第一次渲染的时候
  • 要去创建很多 dom 节点的一个过程,可以大大的提升第一次渲染的性能
  • 看下 react 什么时候需要去执行 hydrate 什么时候又不需要去执行它
  • 在 ReactDOM.js 中找到 react 里面的两个渲染 API
    • 一个是 hydrate 另外一个是 render
    • 它们接收的参数都是一样的
    • 它们都调用同一个函数 legacyRenderSubtreeIntoContainer
    • 唯一的区别是它们传入的第4个参数不一样

2 )源码

定位到 packages/react-dom/src/client/ReactDOM.js#L627

hydrate(element: React$Node, container: DOMContainer, callback: ?Function) {
  // TODO: throw or warn if we couldn't hydrate?
  return legacyRenderSubtreeIntoContainer(
    null,
    element,
    container,
    true,
    callback,
  );
}

render(
  element: React$Element<any>,
  container: DOMContainer,
  callback: ?Function,
) {
  return legacyRenderSubtreeIntoContainer(
    null,
    element,
    container,
    false,
    callback,
  );
}

对于 legacyRenderSubtreeIntoContainer 第4个参数 forceHydrate 表示是否强制融合

function legacyRenderSubtreeIntoContainer(
  parentComponent: ?React$Component<any, any>,
  children: ReactNodeList,
  container: DOMContainer,
  forceHydrate: boolean,
  callback: ?Function,
) {
  // TODO: Ensure all entry points contain this check
  invariant(
    isValidContainer(container),
    'Target container is not a DOM element.',
  );

  if (__DEV__) {
    topLevelUpdateWarnings(container);
  }

  // TODO: Without `any` type, Flow says "Property cannot be accessed on any
  // member of intersection type." Whyyyyyy.
  let root: Root = (container._reactRootContainer: any);
  if (!root) {
    // Initial mount
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
      container,
      forceHydrate,
    );
    if (typeof callback === 'function') {
      const originalCallback = callback;
      callback = function() {
        const instance = DOMRenderer.getPublicRootInstance(root._internalRoot);
        originalCallback.call(instance);
      };
    }
    // Initial mount should not be batched.
    DOMRenderer.unbatchedUpdates(() => {
      if (parentComponent != null) {
        root.legacy_renderSubtreeIntoContainer(
          parentComponent,
          children,
          callback,
        );
      } else {
        root.render(children, callback);
      }
    });
  } else {
    if (typeof callback === 'function') {
      const originalCallback = callback;
      callback = function() {
        const instance = DOMRenderer.getPublicRootInstance(root._internalRoot);
        originalCallback.call(instance);
      };
    }
    // Update
    if (parentComponent != null) {
      root.legacy_renderSubtreeIntoContainer(
        parentComponent,
        children,
        callback,
      );
    } else {
      root.render(children, callback);
    }
  }
  return DOMRenderer.getPublicRootInstance(root._internalRoot);
}
  • 在调用 legacyCreateRootFromDOMContainer 这个方法的时候,会传入这个 forceHydrate 这个参数
    function legacyCreateRootFromDOMContainer(
      container: DOMContainer,
      forceHydrate: boolean,
    ): Root {
      // 在这个方法里面,首先它声明了一个参数叫 shouldHydrate,如果 forceHydrate 为 true, shouldHydrate 也一定为 true
      // 不是 true, 需要调用 `shouldHydrateDueToLegacyHeuristic` 来进行判断
      // 这也是目前在 react dom 环境中,即便使用了 render API,但仍然有可能会去执行 hydrate 的一个过程
      // 注意,只针对目前版本
      const shouldHydrate =
        forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
      // First clear any existing content.
      if (!shouldHydrate) {
        let warned = false;
        let rootSibling;
        while ((rootSibling = container.lastChild)) {
          if (__DEV__) {
            if (
              !warned &&
              rootSibling.nodeType === ELEMENT_NODE &&
              (rootSibling: any).hasAttribute(ROOT_ATTRIBUTE_NAME)
            ) {
              warned = true;
              warningWithoutStack(
                false,
                'render(): Target node has markup rendered by React, but there ' +
                  'are unrelated nodes as well. This is most commonly caused by ' +
                  'white-space inserted around server-rendered markup.',
              );
            }
          }
          container.removeChild(rootSibling);
        }
      }
      if (__DEV__) {
        if (shouldHydrate && !forceHydrate && !warnedAboutHydrateAPI) {
          warnedAboutHydrateAPI = true;
          lowPriorityWarning(
            false,
            'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
              'will stop working in React v17. Replace the ReactDOM.render() call ' +
              'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
          );
        }
      }
      // Legacy roots are not async by default.
      const isConcurrent = false;
      // 后续在创建 ReactRoot 的时候,传入了 shouldHydrate
      return new ReactRoot(container, isConcurrent, shouldHydrate);
    }
    
    • 进入 shouldHydrateDueToLegacyHeuristic
      function shouldHydrateDueToLegacyHeuristic(container) {
        // 这里面我们拿到了rootElement 之后,我们首先判断的条件
        // 就是它是否存在, 以及它是否是一个普通的节点
        // 以及它是否有 `ROOT_ATTRIBUTE_NAME` 这个attribute
        // ROOT_ATTRIBUTE_NAME 的 值是 data-reactroot
        // 这个是在调用服务端渲染的API的时候,生成的HTML上面它的container节点上面会增加这个attribute
        // 这也是react服务端渲染的API帮助我们去在客户端去识别是否需要融合节点的一个帮助的attribute
        // 如果符合这个条件,React它就会猜测, 即便你调用的不是 Hydrate API
        // 它仍然猜测你是需要执行 hydrate,它的 shouldHydrate 就等于 true
        const rootElement = getReactRootElementInContainer(container);
        return !!(
          rootElement &&
          rootElement.nodeType === ELEMENT_NODE &&
          rootElement.hasAttribute(ROOT_ATTRIBUTE_NAME)
        );
      }
      
    • 进入 getReactRootElementInContainer
      function getReactRootElementInContainer(container: any) {
        if (!container) {
          return null;
        }
        // DOCUMENT_NODE 表示 window.document, 返回 documentElement 就是 html节点
        if (container.nodeType === DOCUMENT_NODE) {
          return container.documentElement;
        } else {
          return container.firstChild;
        }
      }
      
      • 这也是强烈不推荐在调用 render API 的时候传入 document 的原因
      • 因为如果传入了document,那么它就会对整个HTML文档来进行一个调和的过程
      • 如果我不需要一个融合的过程,它可能会直接把HTML里面所有的内容都删掉,那么这就很可能会导致一些问题了
      • 所以在这里面,如果是你传入的是 document,它返回的是 documentElement,那就是HTML节点
      • 如果它传入的不是 document, 就是这个 container.firstChild 节点
        • 比如说传入的是root节点,root节点如果它没有子节点,它的firstChild 肯定就是一个 null这种情况
        • 这时候肯定不需要融合的一个过程
    • 进入 ReactRoot
      function ReactRoot(
        container: Container,
        isConcurrent: boolean,
        hydrate: boolean,
      ) {
        const root = DOMRenderer.createContainer(container, isConcurrent, hydrate);
        this._internalRoot = root;
      }
      
      • 进入 createContainer
        export function createContainer(
          containerInfo: Container,
          isConcurrent: boolean,
          hydrate: boolean,
        ): OpaqueRoot {
          return createFiberRoot(containerInfo, isConcurrent, hydrate);
        }
        
        • 进入 createFiberRoot
          export function createFiberRoot(
            containerInfo: any,
            isConcurrent: boolean,
            hydrate: boolean,
          ): FiberRoot {
            // Cyclic construction. This cheats the type system right now because
            // stateNode is any.
            const uninitializedFiber = createHostRootFiber(isConcurrent);
          
            let root;
            if (enableSchedulerTracing) {
              root = ({
                current: uninitializedFiber,
                containerInfo: containerInfo,
                pendingChildren: null,
          
                earliestPendingTime: NoWork,
                latestPendingTime: NoWork,
                earliestSuspendedTime: NoWork,
                latestSuspendedTime: NoWork,
                latestPingedTime: NoWork,
          
                didError: false,
          
                pendingCommitExpirationTime: NoWork,
                finishedWork: null,
                timeoutHandle: noTimeout,
                context: null,
                pendingContext: null,
                hydrate, // 注意这里挂载了
                nextExpirationTimeToWorkOn: NoWork,
                expirationTime: NoWork,
                firstBatch: null,
                nextScheduledRoot: null,
          
                interactionThreadID: unstable_getThreadID(),
                memoizedInteractions: new Set(),
                pendingInteractionMap: new Map(),
              }: FiberRoot);
            } else {
              root = ({
                current: uninitializedFiber,
                containerInfo: containerInfo,
                pendingChildren: null,
          
                earliestPendingTime: NoWork,
                latestPendingTime: NoWork,
                earliestSuspendedTime: NoWork,
                latestSuspendedTime: NoWork,
                latestPingedTime: NoWork,
          
                didError: false,
          
                pendingCommitExpirationTime: NoWork,
                finishedWork: null,
                timeoutHandle: noTimeout,
                context: null,
                pendingContext: null,
                hydrate, // 注意这里挂载了
                nextExpirationTimeToWorkOn: NoWork,
                expirationTime: NoWork,
                firstBatch: null,
                nextScheduledRoot: null,
              }: BaseFiberRootProperties);
            }
          
            uninitializedFiber.stateNode = root;
          
            // The reason for the way the Flow types are structured in this file,
            // Is to avoid needing :any casts everywhere interaction tracing fields are used.
            // Unfortunately that requires an :any cast for non-interaction tracing capable builds.
            // $FlowFixMe Remove this :any cast and replace it with something better.
            return ((root: any): FiberRoot);
          }
          
          • 把 hydrate 挂载上去,也就是告诉后续的流程,这一次渲染是需要 hydrate 的
    • 设置了 root 上面的 hydrate 之后,在后续更新root的时候,比如在 updateHostRoot
    • 在它的更新流程当中会调用一个方法叫做 enterHydrationState 这个方法

定位到 packages/react-reconciler/src/ReactFiberBeginWork.js#L612


function updateHostRoot(current, workInProgress, renderExpirationTime) {
  // ... 跳过很多代码
  if (
    (current === null || current.child === null) &&
    root.hydrate &&
    enterHydrationState(workInProgress) // 注意这里
  ) {
  // ... 跳过很多代码
  return workInProgress.child;
}

这个 enterHydrationState 方法标志着整个 react 应用的 hydrate 过程的一个开启

定位到 packages/react-reconciler/src/ReactFiberHydrationContext.js#L49

function enterHydrationState(fiber: Fiber): boolean {
  // supportsHydration 在 react dom 环境中是 写死的 true
  if (!supportsHydration) {
    return false;
  }

  // 默认是 root, 也就是 ReactDOM.render 中的第2个参数
  const parentInstance = fiber.stateNode.containerInfo;
  // nextHydratableInstance 是一个全局变量
  nextHydratableInstance = getFirstHydratableChild(parentInstance);
  hydrationParentFiber = fiber;
  isHydrating = true;
  return true;
}
  • 进入 getFirstHydratableChild

    export function getFirstHydratableChild(
      parentInstance: Container | Instance,
    ): null | Instance | TextInstance {
      let next = parentInstance.firstChild;
      // Skip non-hydratable nodes.
      // 进入 while 循环
      while (
        next &&
        next.nodeType !== ELEMENT_NODE &&
        next.nodeType !== TEXT_NODE
      ) {
        next = next.nextSibling; // 找到所有星弟节点
      }
      return (next: any);
    }
    
    • 找到 parentInstance 下面第一个 ELEMENT_NODE 或 TEXT_NODE
    • 这其实是一些全局变量的初始化
    • 因为调用这个方法,就代表着我们要正式开始执行 hydrate 的一个过程
    • 因为 FiberRoot 对应的是挂载的那个节点, 它不是我们要渲染的APP里面的对应的节点
    • 所以它并不是一个正式的流程里面的一部分,它只是标志着我们要开始了
  • 开始了之后要做的是什么呢?

    • 主要的集中在 HostComponent 以及 TextComponent
    • 定位到 packages/react-reconciler/src/ReactFiberBeginWork.js#L685
    • 找到 updateHostComponent
      function updateHostComponent(current, workInProgress, renderExpirationTime) {
        pushHostContext(workInProgress);
      
        if (current === null) {
          tryToClaimNextHydratableInstance(workInProgress);
        }
      
        const type = workInProgress.type;
        const nextProps = workInProgress.pendingProps;
        const prevProps = current !== null ? current.memoizedProps : null;
      
        let nextChildren = nextProps.children;
        const isDirectTextChild = shouldSetTextContent(type, nextProps);
      
        if (isDirectTextChild) {
          // We special case a direct text child of a host node. This is a common
          // case. We won't handle it as a reified child. We will instead handle
          // this in the host environment that also have access to this prop. That
          // avoids allocating another HostText fiber and traversing it.
          nextChildren = null;
        } else if (prevProps !== null && shouldSetTextContent(type, prevProps)) {
          // If we're switching from a direct text child to a normal child, or to
          // empty, we need to schedule the text content to be reset.
          workInProgress.effectTag |= ContentReset;
        }
      
        markRef(current, workInProgress);
      
        // Check the host config to see if the children are offscreen/hidden.
        if (
          renderExpirationTime !== Never &&
          workInProgress.mode & ConcurrentMode &&
          shouldDeprioritizeSubtree(type, nextProps)
        ) {
          // Schedule this fiber to re-render at offscreen priority. Then bailout.
          workInProgress.expirationTime = Never;
          return null;
        }
      
        reconcileChildren(
          current,
          workInProgress,
          nextChildren,
          renderExpirationTime,
        );
        return workInProgress.child;
      }
      
      • 可以看到在 current 等于 null 的一个情况下,也就是初次渲染的情况下
      • 它会调用一个方法叫做 tryToClaimNextHydratableInstance 这么一个方法
        function tryToClaimNextHydratableInstance(fiber: Fiber): void {
          // 正在开始 hydrate 这里不会执行 跳过
          if (!isHydrating) {
            return;
          }
          // 这是刚在 enterHydrationState 找到的第一个合法的子节点
          let nextInstance = nextHydratableInstance;
          // 如果这个节点不存在, 说明我们当前正在更新的 HostComponent 是没有节点进行 hydrate 的流程的
          // 所以这个节点它是一个新增的节点,就是我们现在正在更新的这个 HostComponent,它是一个需要去新增的结点
          // 因为这个节点它已经找不到了,在 beginWork 这边更新完成之后,继续更新的是它的子节点,它本身已经不能找到跟它进行复用的一个节点了
          // 它的子节点就更不可能找到复用的子节点了, 所以到这边我们就可以停下来了
          // 对它的子树来说,就不需要再走 hydrate 的流程了,等到回过头来去更新它的兄弟节点的时候,又会重新开始这个 hydrate 的流程
          if (!nextInstance) {
            // Nothing to hydrate. Make it an insertion.
            insertNonHydratedInstance((hydrationParentFiber: any), fiber);
            isHydrating = false;
            hydrationParentFiber = fiber;
            return;
          }
          // 有可以 hydrate 节点的流程
          const firstAttemptedInstance = nextInstance;
          // 判断当前节点是否可以复用,这里是不可以复用
          if (!tryHydrate(fiber, nextInstance)) {
            // If we can't hydrate this instance let's try the next one.
            // We use this as a heuristic. It's based on intuition and not data so it
            // might be flawed or unnecessary.
            nextInstance = getNextHydratableSibling(firstAttemptedInstance);
            // 它又再去进行了一次 tryHydrate 为什么它这里要这么去做呢?
            // 它上面的注释向我们表明了它的一个用意
            // 如果发现这个节点它无法进行headict,我们尝试着去使用它的下一个兄弟节点
            // 这个尝试并不是基于数据分析得来的一个结果而纯粹是我作为一个开发者,直觉认为它可能会出现这样的一个情况
            // 所以这边的代码它并没有一个彻底的原理在里面,它只是出于开发者认为可能会出现这么一个情况
            // 而且加了这一段代码呢也没有太大的一个成本,所以就把它加进去了,可以防止一些很特意的情况出现了一个问题
            if (!nextInstance || !tryHydrate(fiber, nextInstance)) {
              // Nothing to hydrate. Make it an insertion.
              // 如果前后两次进行try的都不成功,说明我们当前的这个节点
              // 没有找到可以复用的那个原生的 dom 节点的,在没有找到的情况,我们就停止 hydrate 操作
              // 设置 isHydrating 为 false 代表着会继续向 hostComponent 的子节点去更新的过程中
              // 如果又遇到了新的 hostComponent,就不会走 hydrate 的一个流程
              insertNonHydratedInstance((hydrationParentFiber: any), fiber);
              isHydrating = false;
              hydrationParentFiber = fiber;
              return;
            }
            // We matched the next one, we'll now assume that the first one was
            // superfluous and we'll delete it. Since we can't eagerly delete it
            // we'll have to schedule a deletion. To do that, this node needs a dummy
            // fiber associated with it.
            deleteHydratableInstance(
              (hydrationParentFiber: any),
              firstAttemptedInstance,
            );
          }
          // 可以复用,找下一个节点,child节点
          hydrationParentFiber = fiber;
          nextHydratableInstance = getFirstHydratableChild((nextInstance: any));
        }
        
        • insertNonHydratedInstance 方法非常简单
          • 给fiber对象加上了 Placement 这个 SideEffect
          • 因为它是一个需要去插入的节点
        • 进入 tryHydrate
          function tryHydrate(fiber, nextInstance) {
            switch (fiber.tag) {
              // 注意这里
              case HostComponent: {
                const type = fiber.type;
                const props = fiber.pendingProps;
                const instance = canHydrateInstance(nextInstance, type, props);
                if (instance !== null) {
                  fiber.stateNode = (instance: Instance); // 注意,这里挂载到了 fiber.stateNode,在后续执行 hydrateInstance 时会直接使用
                  return true;
                }
                return false;
              }
              // 注意这里
              case HostText: {
                const text = fiber.pendingProps;
                const textInstance = canHydrateTextInstance(nextInstance, text);
                if (textInstance !== null) {
                  fiber.stateNode = (textInstance: TextInstance);
                  return true;
                }
                return false;
              }
              default:
                return false;
            }
          }
          
          • 进入 canHydrateInstance
            // packages/react-dom/src/client/ReactDOMHostConfig.js#L462
            export function canHydrateInstance(
              instance: Instance | TextInstance,
              type: string,
              props: Props,
            ): null | Instance {
              if (
                instance.nodeType !== ELEMENT_NODE ||
                type.toLowerCase() !== instance.nodeName.toLowerCase() // 它们标签名不一样
              ) {
                return null; // 这种情况,节点不能复用
              }
              // This has now been refined to an element node.
              return ((instance: any): Instance); // 可以复用并返回
            }
            
          • 进入 canHydrateTextInstance
            export function canHydrateTextInstance(
              instance: Instance | TextInstance,
              text: string,
            ): null | TextInstance {
              if (text === '' || instance.nodeType !== TEXT_NODE) {
                // Empty strings are not parsed by HTML so there won't be a correct match here.
                return null;
              }
              // This has now been refined to a text node.
              return ((instance: any): TextInstance);
            }
            
          • 主要是上述 HostComponent 和 HostText 的处理过程
          • 这个方法主要是判断节点是否可以被复用
            • 比如在 HostComponent 中来帮助我们判断正在更新的这个 hostcomponent
            • 它跟随着 hydrate 的流程一步一步走下来,正处于的那一个原本 dom 树中存在的那个节点
            • 它是否可以被复用这么的一个判断
          • 如果 tryHydrate 是 true 的,就不符合这个条件,就继续向下去找下一个节点了
            • 就通过调用 getFirstHydratableChild
            • 因为按照正常的beginWork的逻辑,接下去要进入的节点是目前正在更新的这个 HostComponent 的child的节点
            • 在它的child节点上面一直往下去找, 进入这个方法
              export function getFirstHydratableChild(
                parentInstance: Container | Instance,
              ): null | Instance | TextInstance {
                let next = parentInstance.firstChild;
                // Skip non-hydratable nodes.
                while (
                  next &&
                  next.nodeType !== ELEMENT_NODE &&
                  next.nodeType !== TEXT_NODE
                ) {
                  next = next.nextSibling;
                }
                return (next: any);
              }
              
          • 而如果 tryHydrate 是 false 的
            • 这里面并没有直接终止 hydrate 的流程,而是去找到了它的 nextHydratableSibling
            • 调用 getNextHydratableSibling 进入它
              export function getNextHydratableSibling(
                instance: Instance | TextInstance,
              ): null | Instance | TextInstance {
                // 获取当前节点的下一个兄弟节点
                let node = instance.nextSibling;
                // Skip non-hydratable nodes.
                // 去遍历兄弟节点, 找到一个符合需求的那么一个节点
                while (
                  node &&
                  node.nodeType !== ELEMENT_NODE &&
                  node.nodeType !== TEXT_NODE
                ) {
                  node = node.nextSibling;
                }
                return (node: any);
              }
              
          • 可见 getNextHydratableSibling 和 getFirstHydratableChild 基本上差不多
            • 只不过一个是遍历的它的兄弟节点,一个是遍历它的子节点当中的兄弟节点
    • 这边在beginWork的流程中,updateHostComponent,它继续执行的更新的是向下它的子节点
    • 如果在我们更新当前节点的时候都没有找到一个可以复用的 dom 节点
    • 它的子节点根本不可能找到一个可以复用的节点来进行对应, 这是很明显的一个情况
    • react在上述工作进行了跳过 hydrate 的一个流程,让后续的代码就不需要去进行一个判断之类的操作了
    • 也节省了一些性能的损耗
    • 这就是在 beginWork 当中去执行了 tryToClaimNextHydratableInstance 这个方法
      • 它的主要目的是判断我们当前的 hostcomponent 是否有可以复用的这个节点
      • 然后去继续往下找,直到找到 beginWork 的一侧子树被 hydrate 完之后
      • 后续就要进入 completeUnitOfWork 了
      • 这样对一侧的子树的更新已经完成了
    • 在后续执行 completeUnitOfWork 的时候,要去真正的创建dom节点了
      • 对于 hostcomponent,是需要去创建 instance
      • 对于 hydrate 的一个流程,就不需要去创建 instance,而是需要去复用原本 dom 就已经存在的这个 instance
  • 在 beginWork 的流程当中,去判断更新的一个 HostComponent

  • 是否可以从 hydrate 的 dom 节点当中去找到一个可以复用的节点

  • 如果不能找到,就对这节点以及它的子树停止一个 hydrate 的流程,然后设置一些全局变量

  • 接下去走的流程是在 completeUnitOfWork

定位到 packages/react-reconciler/src/ReactFiberCompleteWork.js#L540

进入 completeWork


function completeWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderExpirationTime: ExpirationTime,
): Fiber | null {
  const newProps = workInProgress.pendingProps;

  switch (workInProgress.tag) {
    case IndeterminateComponent:
      break;
    case LazyComponent:
      break;
    case SimpleMemoComponent:
    case FunctionComponent:
      break;
    case ClassComponent: {
      const Component = workInProgress.type;
      if (isLegacyContextProvider(Component)) {
        popLegacyContext(workInProgress);
      }
      break;
    }
    case HostRoot: {
      popHostContainer(workInProgress);
      popTopLevelLegacyContextObject(workInProgress);
      const fiberRoot = (workInProgress.stateNode: FiberRoot);
      if (fiberRoot.pendingContext) {
        fiberRoot.context = fiberRoot.pendingContext;
        fiberRoot.pendingContext = null;
      }
      if (current === null || current.child === null) {
        // If we hydrated, pop so that we can delete any remaining children
        // that weren't hydrated.
        popHydrationState(workInProgress);
        // This resets the hacky state to fix isMounted before committing.
        // TODO: Delete this when we delete isMounted and findDOMNode.
        workInProgress.effectTag &= ~Placement;
      }
      updateHostContainer(workInProgress);
      break;
    }
    case HostComponent: {
      popHostContext(workInProgress);
      const rootContainerInstance = getRootHostContainer();
      const type = workInProgress.type;
      if (current !== null && workInProgress.stateNode != null) {
        updateHostComponent(
          current,
          workInProgress,
          type,
          newProps,
          rootContainerInstance,
        );

        if (current.ref !== workInProgress.ref) {
          markRef(workInProgress);
        }
      } else {
        if (!newProps) {
          invariant(
            workInProgress.stateNode !== null,
            'We must have new props for new mounts. This error is likely ' +
              'caused by a bug in React. Please file an issue.',
          );
          // This can happen when we abort work.
          break;
        }

        const currentHostContext = getHostContext();
        // TODO: Move createInstance to beginWork and keep it on a context
        // "stack" as the parent. Then append children as we go in beginWork
        // or completeWork depending on we want to add then top->down or
        // bottom->up. Top->down is faster in IE11.
        let wasHydrated = popHydrationState(workInProgress);
        if (wasHydrated) {
          // TODO: Move this and createInstance step into the beginPhase
          // to consolidate.
          if (
            prepareToHydrateHostInstance(
              workInProgress,
              rootContainerInstance,
              currentHostContext,
            )
          ) {
            // If changes to the hydrated node needs to be applied at the
            // commit-phase we mark this as such.
            markUpdate(workInProgress);
          }
        } else {
          let instance = createInstance(
            type,
            newProps,
            rootContainerInstance,
            currentHostContext,
            workInProgress,
          );

          appendAllChildren(instance, workInProgress, false, false);

          // Certain renderers require commit-time effects for initial mount.
          // (eg DOM renderer supports auto-focus for certain elements).
          // Make sure such renderers get scheduled for later work.
          if (
            finalizeInitialChildren(
              instance,
              type,
              newProps,
              rootContainerInstance,
              currentHostContext,
            )
          ) {
            markUpdate(workInProgress);
          }
          workInProgress.stateNode = instance;
        }

        if (workInProgress.ref !== null) {
          // If there is a ref on a host node we need to schedule a callback
          markRef(workInProgress);
        }
      }
      break;
    }
    case HostText: {
      let newText = newProps;
      if (current && workInProgress.stateNode != null) {
        const oldText = current.memoizedProps;
        // If we have an alternate, that means this is an update and we need
        // to schedule a side-effect to do the updates.
        updateHostText(current, workInProgress, oldText, newText);
      } else {
        if (typeof newText !== 'string') {
          invariant(
            workInProgress.stateNode !== null,
            'We must have new props for new mounts. This error is likely ' +
              'caused by a bug in React. Please file an issue.',
          );
          // This can happen when we abort work.
        }
        const rootContainerInstance = getRootHostContainer();
        const currentHostContext = getHostContext();
        let wasHydrated = popHydrationState(workInProgress);
        if (wasHydrated) {
          if (prepareToHydrateHostTextInstance(workInProgress)) {
            markUpdate(workInProgress);
          }
        } else {
          workInProgress.stateNode = createTextInstance(
            newText,
            rootContainerInstance,
            currentHostContext,
            workInProgress,
          );
        }
      }
      break;
    }
    case ForwardRef:
      break;
    case SuspenseComponent: {
      const nextState = workInProgress.memoizedState;
      if ((workInProgress.effectTag & DidCapture) !== NoEffect) {
        // Something suspended. Re-render with the fallback children.
        workInProgress.expirationTime = renderExpirationTime;
        // Do not reset the effect list.
        return workInProgress;
      }

      const nextDidTimeout = nextState !== null;
      const prevDidTimeout = current !== null && current.memoizedState !== null;

      if (current !== null && !nextDidTimeout && prevDidTimeout) {
        // We just switched from the fallback to the normal children. Delete
        // the fallback.
        // TODO: Would it be better to store the fallback fragment on
        // the stateNode during the begin phase?
        const currentFallbackChild: Fiber | null = (current.child: any).sibling;
        if (currentFallbackChild !== null) {
          // Deletions go at the beginning of the return fiber's effect list
          const first = workInProgress.firstEffect;
          if (first !== null) {
            workInProgress.firstEffect = currentFallbackChild;
            currentFallbackChild.nextEffect = first;
          } else {
            workInProgress.firstEffect = workInProgress.lastEffect = currentFallbackChild;
            currentFallbackChild.nextEffect = null;
          }
          currentFallbackChild.effectTag = Deletion;
        }
      }

      // The children either timed out after previously being visible, or
      // were restored after previously being hidden. Schedule an effect
      // to update their visiblity.
      if (
        //
        nextDidTimeout !== prevDidTimeout ||
        // Outside concurrent mode, the primary children commit in an
        // inconsistent state, even if they are hidden. So if they are hidden,
        // we need to schedule an effect to re-hide them, just in case.
        ((workInProgress.effectTag & ConcurrentMode) === NoContext &&
          nextDidTimeout)
      ) {
        workInProgress.effectTag |= Update;
      }
      break;
    }
    case Fragment:
      break;
    case Mode:
      break;
    case Profiler:
      break;
    case HostPortal:
      popHostContainer(workInProgress);
      updateHostContainer(workInProgress);
      break;
    case ContextProvider:
      // Pop provider fiber
      popProvider(workInProgress);
      break;
    case ContextConsumer:
      break;
    case MemoComponent:
      break;
    case IncompleteClassComponent: {
      // Same as class component case. I put it down here so that the tags are
      // sequential to ensure this switch is compiled to a jump table.
      const Component = workInProgress.type;
      if (isLegacyContextProvider(Component)) {
        popLegacyContext(workInProgress);
      }
      break;
    }
    default:
      invariant(
        false,
        'Unknown unit of work tag. This error is likely caused by a bug in ' +
          'React. Please file an issue.',
      );
  }

  return null;
}
  • 在 completeWork 中,对于 HostComponent 的一个更新
  • current,它等于 null 的情况在这里, 会先去执行一个方法叫做 popHydrationState
  • 这个方法就是我们开始要去准备复用这个节点的流程了
    function popHydrationState(fiber: Fiber): boolean {
      // 这里直接跳过,不会执行
      if (!supportsHydration) {
        return false;
      }
      if (fiber !== hydrationParentFiber) {
        // We're deeper than the current hydration context, inside an inserted
        // tree.
        return false;
      }
      if (!isHydrating) {
        // If we're not currently hydrating but we're in a hydration context, then
        // we were an insertion and now need to pop up reenter hydration of our
        // siblings.
        popToNextHostParent(fiber);
        isHydrating = true;
        return false;
      }
    
      const type = fiber.type;
    
      // If we have any remaining hydratable nodes, we need to delete them now.
      // We only do this deeper than head and body since they tend to have random
      // other nodes in them. We also ignore components with pure text content in
      // side of them.
      // TODO: Better heuristic.
      if (
        fiber.tag !== HostComponent ||
        (type !== 'head' &&
          type !== 'body' &&
          !shouldSetTextContent(type, fiber.memoizedProps))
      ) {
        let nextInstance = nextHydratableInstance;
        while (nextInstance) {
          deleteHydratableInstance(fiber, nextInstance);
          nextInstance = getNextHydratableSibling(nextInstance);
        }
      }
    
      popToNextHostParent(fiber);
      nextHydratableInstance = hydrationParentFiber
        ? getNextHydratableSibling(fiber.stateNode)
        : null;
      return true;
    }
    
    • 关于 fiber !== hydrationParentFiber 如下图解释
  • 在这张图里面,其中有 div, input, span和 button 这几个是原生的dom节点
  • 在第一次渲染的时候,要去尝试执行 hydrate 的, 先回到 beginWork 的流程
  • 在 beginWork 当中,调用了 tryToClaimNextHydratableInstance 的时候,
  • 在 enterHydrationState 的里面,设置了这些初始化的变量
    function enterHydrationState(fiber: Fiber): boolean {
      // supportsHydration 在 react dom 环境中是 写死的 true
      if (!supportsHydration) {
        return false;
      }
    
      // 默认是 root, 也就是 ReactDOM.render 中的第2个参数
      const parentInstance = fiber.stateNode.containerInfo;
      // nextHydratableInstance 是一个全局变量
      nextHydratableInstance = getFirstHydratableChild(parentInstance);
      hydrationParentFiber = fiber;
      isHydrating = true;
      return true;
    }
    
  • 然后,它的 nextHydratableInstance 是等于 getFirstHydratableChild(parentInstance)
  • 对于这张图里面的情况,它找到的它第一个可以使用的节点,它肯定是 div 这个节点
  • 也就是说执行了这个 enterHydrationState 方法之后,这个变量它变成了 div
  • 然后这个 hydrationParentFiber 是目前传进来的 fiber,也就是这个 RootFiber
  • 等到在div这个节点更新的时候,我们会执行这个 tryToClaimNextHydratableInstance 方法
  • 执行这个方法的时候,if (!tryHydrate(fiber, nextInstance)) {}, 这个 tryHydrate 是成功的
  • 成功了之后会设置 hydrationParentFiber = fiber; 这里的fiber就是当前fiber, 也就是 div 所对应的fiber
  • 而 nextHydratableInstance 是上图的 input,因为div要找到它一侧的子节点
  • 对于这个div节点来说,input span和button都是它的子节点,第一个符合条件的就是input
  • 找到这个input之后,我们对于 beginWork 中的 updateHostComponent,也就是这个input节点的时候,我们又会去执行这个方法
  • 继续执行这个方法之后,我们的 hydrationParentFiber 变成了 input,然后 nextHydratableInstance 变成null了,因为没有子节点了
    export function getFirstHydratableChild(
      parentInstance: Container | Instance,
    ): null | Instance | TextInstance {
      let next = parentInstance.firstChild; // 这时候是 null
      // Skip non-hydratable nodes.
      // 跳过 while 循环
      while (
        next &&
        next.nodeType !== ELEMENT_NODE &&
        next.nodeType !== TEXT_NODE
      ) {
        next = next.nextSibling; // 找到所有星弟节点
      }
      return (next: any); // 最终 return null
    }
    
  • 到这里为止,我们的 beginWork 已经从一侧子树更新完了
  • 这个时候要对 input 执行 completeUnitOfWork
  • 那么 completeUnitOfWork 它首先执行的是 popHydrationState, 再次回到这个方法
      function popHydrationState(fiber: Fiber): boolean {
        // 这里直接跳过,不会执行
        if (!supportsHydration) {
          return false;
        }
        // 这个条件肯定是不符合的, 两者是相等的
        if (fiber !== hydrationParentFiber) {
          // We're deeper than the current hydration context, inside an inserted
          // tree.
          return false;
        }
        if (!isHydrating) {
          // If we're not currently hydrating but we're in a hydration context, then
          // we were an insertion and now need to pop up reenter hydration of our
          // siblings.
          popToNextHostParent(fiber);
          isHydrating = true;
          return false;
        }
    
        const type = fiber.type;
    
        // If we have any remaining hydratable nodes, we need to delete them now.
        // We only do this deeper than head and body since they tend to have random
        // other nodes in them. We also ignore components with pure text content in
        // side of them.
        // TODO: Better heuristic.
        if (
          fiber.tag !== HostComponent ||
          (type !== 'head' &&
            type !== 'body' &&
            !shouldSetTextContent(type, fiber.memoizedProps))
        ) {
          let nextInstance = nextHydratableInstance;
          while (nextInstance) {
            deleteHydratableInstance(fiber, nextInstance);
            nextInstance = getNextHydratableSibling(nextInstance);
          }
        }
    
        popToNextHostParent(fiber);
        nextHydratableInstance = hydrationParentFiber
          ? getNextHydratableSibling(fiber.stateNode)
          : null;
        return true;
      }
    
  • if (fiber !== hydrationParentFiber) {} 这个条件不符合,目前两者相等
  • 那么这个条件什么时候会符合呢?
    • 就是之前在 tryToClaimNextHydratableInstance 的时候
    • 去执行 tryHydrate 比如说执行对于 div 的一个 tryHydrate,发现找不到对应的节点可以复用
    • 那么这个时候我们设置的 hydrationParentFiber 是 div, 然后停止了 hydrate
    • 这个时候更新到input节点的时候,是不会执行这个 tryToClaimNextHydratableInstance 的方法
    • 执行到头部的 if (!isHydrating) 之后,它就直接 return 了,后面的逻辑是完全不会执行到的
    • 所以这个时候 hydrationParentFiber 仍然是 div
    • 所以对 input 执行 completeUnitOfWork 的时候,if (fiber !== hydrationParentFiber) {} 它们这个就不会相等了
    • 就直接return false 就可以了,因为它不能被复用
  • 接下来 if (!isHydrating) {} 什么情况下它会是 false 呢?
    • 就是说在之前 tryToClaimNextHydratableInstance 的时候, if (!tryHydrate(fiber, nextInstance)) {}, 它是不成功的这么一个情况
    • 这个时候 isHydrating 就会变成false,但是要注意的是如果它能执行到这条代码说明,在这一个节点上发现它是不能复用的
    • 而所有不能复用的节点都是它或者它的子节点,所以它的父节点是可以复用的
    • 接下去要执行 completeUnitOfWork 的是 div,这时候就要设置 isHydrating 为 true 了
    • 同时,这边它就执行了 popToNextHostParent 这个方法
      function popToNextHostParent(fiber: Fiber): void {
        let parent = fiber.return;
        while (
          parent !== null &&
          parent.tag !== HostComponent &&
          parent.tag !== HostRoot
        ) {
          parent = parent.return;
        }
        hydrationParentFiber = parent;
      }
      
    • 它就是往它的父链上面去找,第一个是 HostComponent 或者是 HostRoot 的节点就可以了
  • 在 if (!isHydrating) {} 这边最后 return false
    • 因为它本身它这个节点是不能被 hydrate 的
  • 然后在以上3个 if 条件都不符合的情况下, 说明我们这个节点是可以复用的
  • 可以复用的情况,做了什么事情呢?
    • if (fiber.tag !== HostComponent || (type !== 'head' && type !== 'body' && !shouldSetTextContent(type, fiber.memoizedProps))) {}
    • 它判断了是否等于body,head,并且 shouldSetTextContent 这个方法
      export function shouldSetTextContent(type: string, props: Props): boolean {
        return (
          type === 'textarea' ||
          type === 'option' ||
          type === 'noscript' ||
          typeof props.children === 'string' ||
          typeof props.children === 'number' ||
          (typeof props.dangerouslySetInnerHTML === 'object' &&
            props.dangerouslySetInnerHTML !== null &&
            props.dangerouslySetInnerHTML.__html != null)
        );
      }
      
      • 它其实就是判断了一下是否是input相关的标签
    • 符合上述if这个时候,就是一个 HostText
      • 既然它是一个 HostText,如果当前这边的正在要被 hydrate 的这个节点它存在的话就是不合理的
      • 因为要是把当前节点都替换成纯文字的, 所以在这里,它直接把所有的节点都给它删除了,就执行这个 deleteHydratableInstance
        function deleteHydratableInstance(
          returnFiber: Fiber,
          instance: HydratableInstance,
        ) {
          // 跳过
          if (__DEV__) {
            // ... 忽略
          }
          // 创建一个待删除的fiber节点
          const childToDelete = createFiberFromHostInstanceForDeletion();
          childToDelete.stateNode = instance;
          childToDelete.return = returnFiber;
          childToDelete.effectTag = Deletion; // 注意这里
        
          // This might seem like it belongs on progressedFirstDeletion. However,
          // these children are not part of the reconciliation list of children.
          // Even if we abort and rereconcile the children, that will try to hydrate
          // again and the nodes are still in the host tree so these will be
          // recreated.
          if (returnFiber.lastEffect !== null) {
            returnFiber.lastEffect.nextEffect = childToDelete;
            returnFiber.lastEffect = childToDelete;
          } else {
            returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;
          }
        }
        
        • 其实这边做法是比较奇怪的一个做法
        • 单独去创建了一个fiber对象,然后给这个fiber对象指定了 effectTag 是 Deletion
        • 就是我们要通过这个删除节点,也要放到 commitRoot 里面去做
        • 这个节点,在真正的react应用当中,它是没有对应的fiber节点的,它只是在dom当中存在
        • 为了要删除它,需要单独去为它创建一个fiber对象,然后把它挂到 returnFiber 的 effect的链上
        • 最终在 commitRoot 的时候能够执行到这个effect,然后对它执行一个删除的操作,最终把dom节点删除
        • 这样做的目的,是为了统一整个react更新的流程,所以单独这么去做的一个事情
      • 这是对 HostText 的一个处理,接下去就执行 popToNextHostParent
      • 因为接下去要执行 completeWork 的是它的 parent 上面的 host 节点
      • 然后 一个三目运算,成立,通过 getNextHydratableSibling 是找它的兄弟节点
      • 如果不成立,返回 null
      • 这边为什么要这么做呢?
        • 首先它返回 null 是可以理解的,因为它这个节点已经没有父节点了
        • 在父链上已经没有找到可以去被 hydrate 的节点
        • 那么说明这个节点它就是一个 FiberRoot,这个 null 肯定是可以的
        • 如果有的话,为什么要去找它的兄弟节点呢?
          • 因为如果有兄弟节点,在 completeUnitOfWork 的过程当中,说明它也应该存在当前节点是有兄弟节点的
          • 有兄弟节点,就在 completeUnitOfWork 中, 是会返回这个兄弟节点
          • 并对这个兄弟节点执行 beginWork 这个流程
          • 所以在这种情况下,肯定是要走这个流程的,它就去找它的兄弟节点
        • 如果这边是没有兄弟节点,它就变成null了,这也是没有任何问题的
          • 在唯一一个要使用到 nextHydratableInstance 的地方是删除它的节点
          • 因为它是 null 就不会进入到 while循环,它也没有任何的关系
  • 如果是 null 会不会对后面的流程有影响呢?
    • 接着在 completeWork 当中, wasHydrated 是 true 之后
    • 会调用一个方法叫做 prepareToHydrateHostInstance
      function prepareToHydrateHostInstance(
        fiber: Fiber,
        rootContainerInstance: Container,
        hostContext: HostContext,
      ): boolean {
        if (!supportsHydration) {
          invariant(
            false,
            'Expected prepareToHydrateHostInstance() to never be called. ' +
              'This error is likely caused by a bug in React. Please file an issue.',
          );
        }
      
        const instance: Instance = fiber.stateNode; // fiber.stateNode 一开始就已经被挂载了, 这时候是 null
        const updatePayload = hydrateInstance(
          instance, // null
          fiber.type,
          fiber.memoizedProps,
          rootContainerInstance, // hostRoot 对应的 container
          hostContext,
          fiber,
        );
        // TODO: Type this specific to this type of component.
        fiber.updateQueue = (updatePayload: any);
        // If the update payload indicates that there is a change or if there
        // is a new ref we mark this as an update.
        if (updatePayload !== null) {
          return true;
        }
        return false;
      }
      
      • 进入 hydrateInstance
        export function hydrateInstance(
          instance: Instance, 
          type: string,
          props: Props,
          rootContainerInstance: Container,
          hostContext: HostContext,
          internalInstanceHandle: Object,
        ): null | Array<mixed> {
          precacheFiberNode(internalInstanceHandle, instance);
          // TODO: Possibly defer this until the commit phase where all the events
          // get attached.
          updateFiberProps(instance, props);
          let parentNamespace: string;
          if (__DEV__) {
            const hostContextDev = ((hostContext: any): HostContextDev);
            parentNamespace = hostContextDev.namespace;
          } else {
            parentNamespace = ((hostContext: any): HostContextProd);
          }
          return diffHydratedProperties(
            instance,
            type,
            props,
            parentNamespace,
            rootContainerInstance,
          );
        }
        
        • 这个 precacheFiberNode 在之前执行 finalizeInitialChildren 内部,也会执行这个方法
          • 就是在 node 上面挂载了 fiber 的一个引用
        • 之后执行 updateFiberProps 就是通过不同的节点来执行了一些属性
          export function updateFiberProps(node, props) {
            node[internalEventHandlersKey] = props;
          }
          
        • 接下去是重点,return diffHydratedProperties
          • 这个方法其实它就是对比了原生的dom节点上面的属性,以及在 react 渲染中拿到的 props 它们之间的一个情况
          • 然后,它也会生成一个 updatePayload
          • 在 prepareToHydrateHostInstance 里面, 去执行 hydrateInstance 的时候
          • 它返回的是一个 updatepayload,并且设置到了 fiber.updateQueen 上面
          • 最终它就直接return是true还是false了,以此来判断是否这个节点要更新
          • 这个就跟之前的更新流程是非常相似的,它通过 updateQueen 去记录了这个dom节点,它需要更新的属性
          • 最终在 commitRoot 的阶段,会把这些更新都应用到这个 dom 节点上面
          • 源码,参考: packages/react-dom/src/client/ReactDOMComponent.js#L834
          • 它跟 updateProperties 唯一的区别是
            • 这个node,它本身也是由react来创建的,已经存储了一些信息了
            • 而 hydrate 它的一个流程当中的这个dom节点不是react创建的
          • 所以它的对比过程可能会稍微更复杂一点,那其实也是差不多的
            • 比如,要去绑定事件,验证 props
            • 对应每一个props 去判断一下在原生的那个节点上面是否存在
            • 如果有存在,它就不加到 updatePayload
            • 如果不存在,那都加进去,所以其实就是这么一些流程
          • 这些代码非常的长是跟dom操作相关性比较大的一些内容
          • 这些是涉及到 dom 操作的内容,都是比较基础的内容,不涉及react核心
    • 在最初的 tryHydrate 中,已经执行 fiber.stateNode = (instance: Instance)
    • 这里,fiber.stateNode 已经被赋值了其上 canHydrateInstance 得到的 instance, 也即是从dom节点上找到的instance
    • 在后续 hydrateInstance 中的 instance 参数就是 fiber.stateNode,不需要使用在 popHydrationState 中的 nextHydratableInstance 属性了
    • 所以, 在上边 popHydrationState 的时候,nextHydratableInstance 最终拿到是 null 也没有任何关系
      • 因为这个节点是可以 hydrate 的
      • 它的节点已经对应拿到了
      • 这个 nextHydratableInstance 只是一个标识的量
  • 以上,在 beginWork 复用了这个节点之后
  • 并且已经进行了 diffHydratedProperties 了
  • 说明我们这个节点已经复用成功了
  • 之后就是走后面的 commitRoot 流程等内容了
React Devtools 原理与实战:透视 React 运行时的调试利器 React Devtools 是理解 React 运行时机制的核心工具,其本质是通过浏览器扩展架构,深度集成 React 内部协调器(Reconciler)与 Fiber 树,实现实时组件状态、Hook 调用链与渲染生命周期的可视化。它不依赖 Chrome DevTools Protocol,而是利用 `__REACT_DEVTOOLS_GLOBAL_HOOK__` 私有钩子,在 `commit` 阶段前捕获精准变更数据,支撑 `react diff算法原理` 的实证分析与 `react面试题` 中高频考点 阅读详情

相关推荐

React密码强度检测实战:基于zxcvbn的生产级Meter实现

密码强度检测是Web安全的基础能力,其本质是评估密码在真实攻击场景下的不可预测性,而非简单规则匹配。传统正则校验仅检查字符存在性,无法识别'password123'等模式化弱密码;而zxcvbn通过字典匹配、熵值计算与攻击路径模拟,提供符合NIST推荐的工程化解决方案。它支持多语言词典、可解释反馈与动态策略,已在GitHub、Dropbox等系统长期验证。本文聚焦React生态,详解如何规避主线程阻塞、实现Tree Shaking优化、保障无障碍访问,并将zxcvbn深度集成至表单验证流程,输出可直接落地的

weixin_33806914的博客 401

React16中render和hydrate的区别

render() ReactDOM.render(<App />, document.getElementById('root')); render话不多说就是渲染的意思,官方解释: 在提供的container里渲染一个 React 元素,并返回对该组件的引用(或者针对无状态组件返回null)。 如果 React 元素之前已经在container里渲染过,这将会对其执行更新操作,并仅会在必要时改变 DOM 以映射最新的 React 元素 如果提供了可选的回调函数,该回调将在...

halations的博客 5215

Web性能优化实战指南:从30条经验到现代决策树

Web性能优化本质是浏览器渲染机制、JavaScript引擎原理与工程权衡的综合实践。理解FCP、LCP、CLS等核心指标背后的渲染流水线(HTML解析→样式计算→布局→绘制→合成),掌握V8引擎的JIT编译、Hidden Class、String Rope等底层机制,才能避免将过时经验当作银弹。现代前端中,DOM操作性能差异已转向DocumentFragment与innerHTML的安全可控性权衡,eval和字符串setTimeout的危害在于破坏JIT优化而非单纯执行慢,而table布局问题早已超越渲染

clugcpne10995的博客 419

手把手教你实现React SSR服务端渲染【含demo实现】

渲染模式其实远不止以上几种,很多场景下都可以进行相应的优化。以下是一些我能想到的场景:在录入或更新数据时通过WebHook等通知构建系统进行增量构建,算是ISR的一种变种在SSR场景下可以对静态组件和动态组件进行区分,将静态组件使用SSG输出,然后将其拼接到页面中。所以没有最好的只有最适合的,按需选择最适合自己需求的渲染模式即可。

daisyboom的博客 5675

ECharts智慧景区大屏源码:指挥中心级数据可视化实战

数据可视化是智慧景区建设的核心能力,其本质是将多源异构的客流、安防、环境等实时数据,通过可视化技术转化为可感知、可决策的运营态势。ECharts作为主流开源图表库,常被用于大屏展示,但真实指挥中心场景要求远超基础渲染——需兼顾IE11兼容、低延迟响应、高并发承载与7×24稳定运行。本文聚焦工程落地视角,解析如何将ECharts从‘绘图工具’升级为‘数据调度中枢’,结合GIS地图融合、Web Worker预计算、模块化数据域设计等关键技术,构建可对接票务/监控/广播系统的生产级可视化系统,适用于景区管委会、集

weixin_34088583的博客 397

手把手教你用STM32CubeMX和HAL库驱动MT6835磁编码器(SPI读取21位角度)

本文详细介绍了如何使用STM32CubeMX和HAL库驱动MT6835磁编码器,通过SPI接口读取21位高精度角度数据。从硬件准备、CubeMX配置到HAL库驱动实现与优化,提供了完整的实战指南,帮助开发者快速掌握磁编码器的应用技巧,适用于机器人关节和精密仪器等高精度场景。

weixin_28716769的博客 439

React 18已经发布,新特性一览,带你快速了解。

React 18发布,带来一些行的 API 特性,本文带你一览新特性。

FarmerLZJ的专栏 6185

基于h5端的视频录制和回溯功能的实现

整理了一套《前端大厂面试宝典》,包含了HTML、CSS、JavaScript、HTTP、TCP协议、浏览器、VUE、React、数据结构和算法,一共201道面试题,并对每个问题作出了回答和解析。

发果叁 3193

Relay 性能优化实战指南:从数据预取到渲染粒度的系统化性能调优

Relay 是构建数据驱动 React 应用的 JavaScript 框架,其性能优势并非自动获得,而是取决于查询放置、`@defer` 流式加载、fetch policy 选择、垃圾回收配置与 fragment 粒度等系统性实践。本文以仓库 `skills/relay-performance/SKILL.md` 为骨架,结合 `relay-runtime`、`react-relay` 与 Rus

gitblog_00259的博客 482

从部署到养成:Hermes智能体接入DeepSeek与微信机器人的完整实战指南

智能体(Agent)正成为大模型从“对话”走向“行动”的关键形态。它通过拆解指令、调用工具、执行任务,让AI真正参与工作流。Hermes作为可本地部署的开源智能体框架,支持接入DeepSeek等主流大模型,并提供网关、工具调用、记忆管理等模块化能力。从Docker容器部署到微信机器人桥接,从自动化定时任务到Computer Use桌面控制,Hermes将个人助理从概念落地为可运行的工程系统。本文基于真实实践,梳理架构原理、部署流程、API配置与高频故障排查,为技术玩家提供一套从零到一的上手指南。

weixin_29045001的博客 265

别急着让AI画板!聊聊Cadence Allegro X AI和OrCAD X在实际PCB项目中的真实体验

本文分享了Cadence Allegro X AI与OrCAD X在PCB设计中的实际应用体验,探讨了AI辅助Layout的效率提升与潜在陷阱。通过高速ADC采集板案例,详细对比了AI与传统方法在电源平面分割、低速信号布线等任务中的表现,并总结了人机协作的最佳实践,为工程师提供实用参考。

weixin_30836759的博客 2228

Node服务端为什么能直接运行JSX?react-ssr中babel-register机制深度剖析

🔍 **React 服务端渲染(SSR)** 是提升首屏速度与 SEO 的关键技术,但一个新手常有的疑问是:浏览器靠编译才能理解 JSX,为什么 **Node.js 服务端**却可以"直接运行" JSX?本文以开源项目 **react-ssr**(一个专为 React 服务端渲染打造的完整教学基线)为例,深度剖析 [index.js](https://link.gitcode.com/i/375

gitblog_00186的博客 220

ArcGIS Pro中协同区位熵方法在商业选址分析中的应用

本文详细介绍了如何利用ArcGIS Pro中的协同区位熵方法进行科学的商业选址分析。该方法通过量化目标客户与竞争对手在空间上的关联程度,精准识别市场空白区与竞争红海,帮助决策者从“凭感觉”转向“用数据说话”,有效提升选址成功率和投资回报。

weixin_29214559的博客 362

react解析: render的FiberRoot(三)

react解析: render的FiberRoot(三) 感谢yck: 剖析 React 源码解析,本篇文章是在读完他的文章的基础上,将他的文章进行拆解和加工,加入我自己的一下理解和例子,便于大家理解。觉得yck写的真的很棒 。React 版本为 16.8.6,关于源码的阅读,可以移步到yck react源码解析 本文永久有效链接:react解析 render的FiberRoo...

weixin_30376163的博客 309

Python爬虫实战:Playwright 攻克京东图书新书榜(含动态价格与分页)!

我长期专注 Python 爬虫工程化实战,主理专栏 《Python爬虫实战》:从采集策略到反爬对抗,从数据清洗到分布式调度,持续输出可复用的方法论与可落地案例。内容主打一个“能跑、能用、能扩展”,让数据价值真正做到——抓得到、洗得净、用得上。

喵手的博客 595

基于逆向工程的中国裁判文书网爬虫技术深度解析:从反爬破解到合规抓取

最近承接了一个法律大数据分析的项目,需要获取某类合同纠纷的裁判文书数据,用于分析不同地区的判决规律、法官的裁判倾向以及法条适用情况。原本以为用常规的Python爬虫就能搞定,结果刚对着中国裁判文书网发了几个请求,就遭遇了“滑铁卢”:要么请求直接返回403 Forbidden,要么触发滑块验证码,甚至连IP都被临时封禁了。普通的requests+BeautifulSoup组合在裁判文书网面前完全失效,这也让我意识到,想要获取这里的数据,必须先通过逆向工程拆解网站的请求逻辑和反爬机制。经过一周的摸索,从抓包分析

专注于Python爬虫开发,分享爬虫技巧、项目实战与反爬经验,使用Scrapy、BeautifulSoup等工具,解决数据抓取难题。 3173

React 16 源码瞎几把解读 【三 点 二】 react中的fiberRoot

〇、先来看看常用的常量 NoWork = 0 noTimeout = undefined HostRoot = 3 NoContext = 0b000; AsyncMode = 0b001; StrictMode = 0b010; ProfileMode = 0b100; NoEffect = /* */ 0b00000000000 enableProf...

weixin_30879833的博客 153

大数据预测分析:精准把握市场趋势

传统市场预测多依赖经验判断(比如“去年卖了1000件,今年加20%”)或小数据统计(比如用近6个月的销量做线性回归)。而大数据预测分析是:利用海量结构化+非结构化数据(销售、用户、社交媒体、天气等),通过机器学习/深度学习模型,挖掘数据中的关联关系与趋势规律,实现对未来市场走势的精准预测。更全面:覆盖内部(企业数据)+外部(行业、环境数据)的全维度数据;更精准:用算法替代人工,避免主观偏差;更实时:支持分钟级/小时级的实时预测(比如直播带货的销量预测)。错误做法。

AI智能探索者的博客 1025

PaddleHub 图像分类模块 esnet_x0_5_imagenet:从安装、API 预测到 Serving 部署的完整实战指南

本文以 PaddleFormers 仓库中 [esnet_x0_5_imagenet](https://link.gitcode.com/i/e3a14f0489723548be0e5eba12aea873) 模块为主线,系统讲解百度自研轻量级网络 ESNet 在 PaddleHub 平台上的使用方式。你将掌握该模块的环境安装、命令行与代码级预测 API、`top_k` 等关键参数含义,以及基于

gitblog_00150的博客 660

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

Preact SSR实战:Unistore状态同步与Router同构路由详解

服务端渲染(SSR)本质是将前端组件在Node.js环境中预先生成HTML字符串,以提升首屏性能与SEO。其核心原理在于服务端与客户端共享同一套组件逻辑,但执行环境差异导致document、window等浏览器API不可用,必须通过同构设计规避。Preact凭借轻量内核和无Fiber调度的特性,在SSR场景下具备更小体积与更快渲染速度;Unistore作为极简状态库,虽不内置服务端序列化支持,却因无副作用而易于控制初始状态传递;Preact Router则依赖显式url prop实现服务端静态匹配,避免hy

diaoju3333的博客 400

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

Taro跨端开发入门:从环境搭建到多端发布实战指南

跨端开发框架通过统一技术栈实现多平台应用构建,其核心原理在于编译时进行代码转换与运行时提供API适配,从而显著提升开发效率并降低维护成本。这一技术价值在追求快速迭代和广泛覆盖的移动互联网场景中尤为突出,尤其适合需要同时覆盖微信、支付宝、抖音、百度等多个小程序平台以及H5和React Native的应用项目。Taro作为主流跨端解决方案,其“一次编写,多端运行”的能力精准契合了“流量主”等对开发效率有极致要求的群体需求。本文以Taro为例,详细解析其环境搭建、项目创建、核心工作逻辑及多端适配实战,帮助开发者快

weixin_33951761的博客 761

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

Esmx 是什么?下一代零运行时微前端框架的完整入门指南

Esmx 是一款基于原生 ESM 的下一代微前端框架,主打"无沙箱、零运行时开销",让 Vue、React、Preact、Solid 等多种框架在同一页面混合运行。如果你正在寻找比 qiankun、Module Federation 更轻量、更符合 Web 标准的微前端方案,这篇零运行时微前端框架入门指南将带你从原理到实践快速上手。 [![微前端框架 Esmx 多框架混合运行演示面板](http

gitblog_00526的博客 847

HTML Anything:所见即所得的Next.js+Tailwind可视化前端开发器

HTML编辑器本质上是将设计意图转化为语义化、SEO友好、移动端适配的静态HTML的过程。其核心原理在于解耦‘内容结构’与‘呈现逻辑’,通过React组件化实现可复用UI模块,借助Tailwind原子类体系保障样式精准可控,并依托Next.js的SSG/ISR能力达成毫秒级实时预览与开箱即用的部署体验。这种技术组合不仅大幅降低HTML开发门槛,更让市场运营、产品经理和全栈开发者能快速交付生产级页面。而Claude Code的深度集成,则进一步打通了自然语言描述到可编辑UI区块的双向映射,使HTML编辑从手写

weixin_33912638的博客 301

ELPI:可嵌入OCaml的高阶逻辑推理引擎,解决绑定器与规则难题

从逻辑编程与规则引擎的基本需求谈起,传统一阶Prolog在处理带绑定器、函数变量等复杂结构时显得力不从心。高阶逻辑(Lambda Prolog)将变量、作用域和代换作为一等公民,使得规则描述更接近数学语义。ELPI作为可嵌入OCaml的高阶逻辑解释器,通过高阶统一和HOAS(高阶抽象语法)机制,让绑定器管理、α等价和约束传播成为语言内置能力。它不仅是简单解释器,更是可与宿主程序协同工作的推理组件,适用于Coq元编程、AST分析、可定制规则引擎等场景。文章从核心机制、环境搭建到嵌入工程实践,为开发者提供从入门

weixin_34195364的博客 459

axios-cache-interceptor 常见问题解答:解决开发中遇到的20个典型问题

axios-cache-interceptor 是一个功能强大的 Axios 缓存拦截器库,能够帮助开发者轻松实现 HTTP 请求的智能缓存功能。🚀 在使用这个库的过程中,开发者经常会遇到各种配置和使用问题。本文将为您解答20个最常见的问题,帮助您快速解决开发中的困扰。 ## 📦 1. 如何正确安装和配置 axios-cache-interceptor? 许多开发者在使用 axios-ca

gitblog_00790的博客 991
上一篇: React16源码: React中处理ref的核心流程源码实现
下一篇: React16源码: React中event事件系统初始化源码实现
Wang's Blog
博客等级 码龄12年 7438粉丝 2421原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wang's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值