react-dom 源码(1)总览

1,077 阅读2分钟

本文基于 v16.12.0 版本

ReactDom 对象

ReactDom 暴露出来的 API 如下:

var ReactDOM = {
  createPortal: createPortal?1,
  // Legacy
  findDOMNode: findDOMNode,
  hydrate: hydrate,
  render: render,
  unstable_renderSubtreeIntoContainer: unstable_renderSubtreeIntoContainer,
  unmountComponentAtNode: unmountComponentAtNode,
  // TODO: remove in React 17.
  unstable_createPortal: function () {...},
  unstable_batchedUpdates: batchedUpdates$1,
  flushSync: flushSync,
  __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: {
    // Keep in sync with ReactDOMUnstableNativeDependencies.js
    // ReactTestUtils.js, and ReactTestUtilsAct.js. This is an array for better minification.
    Events: [getInstanceFromNode$1, getNodeFromInstance$1, getFiberCurrentPropsFromNode$1, injection.injectEventPluginsByName, eventNameDispatchConfigs, accumulateTwoPhaseDispatches, accumulateDirectDispatches, enqueueStateRestore, restoreStateIfNeeded, dispatchEvent, runEventsInBatch, flushPassiveEffects, IsThisRendererActing]
  }
};

render 与 hydrate

function hydrate(element, container, callback) {
  //...
  return legacyRenderSubtreeIntoContainer(null, element, container, true, callback);
}
function render(element, container, callback) {
  //...
  return legacyRenderSubtreeIntoContainer(null, element, container, false, callback);
}
function unstable_renderSubtreeIntoContainer(parentComponent, element, containerNode, callback) {
  //...
  return legacyRenderSubtreeIntoContainer(parentComponent, element, containerNode, false, callback);
}
  • 如上代码可以看出,render / hydrate / unstable_renderSubtreeIntoContainer 实际上都是内部方法 legacyRenderSubtreeIntoContainer 的加壳方法;
  • 唯一区别在于传入 legacyRenderSubtreeIntoContainer 方法的第四个参数,render 为 false,hydrate 为 true,在 legacyRenderSubtreeIntoContainer 中为 forceHydrate 参数,用来标示是否采用“水合”方式 render;
  • hydrate 是用于 SSR 服务端渲染的;而 render 在 v17 版本之后,将只用于 SPA 客户端渲染,因为用在 SSR 下速度较慢;

hydrate

官方 api 说明如下:

If you call ReactDOM.hydrate() on a node that already has this server-rendered markup, React will preserve it and only attach event handlers, allowing you to have a very performant first-load experience.

译文:

如果在已经具有服务器渲染标记的节点上调用 ReactDOM.hydrate() ,React 将保留它并仅附加事件处理程序,从而使您获得非常出色的首次加载体验。

引入原因:

引入版本:Add ReactDOM.hydrate() as explicit SSR hydration API #10339

官方说明如下:

  • Removes data-reactid from new SSR.
  • We now use presence of data-reactroot as heuristic for hydration.
  • There is also an explicit ReactDOM.hydrate() API that never clears the existing content.
  • ReactDOM.render() will now print a deprecation message when it tries to reuse markup.
  • Downgrade deprecation message to lowPriorityWarning.
  • A warning if we tried to hydrate but there’s no DOM to hydrate into.

This means ReactDOM.render() would still attempt to hydrate when there’s an element root, but might not work for new features (like top level strings or fragments). ReactDOM.hydrate() would always hydrate.

译文:

  • 从新的 SSR 中删除了 data-reactid。
  • 现在,我们将 ==data-reactroot== 的存在作为启发来进行水合作用。
  • 引入一个显式的 ReactDOM.hydrate() API,它永远不会清除现有内容。
  • 当使用 ReactDOM.render() 重用标记时,它将打印一条弃用消息。
  • 将弃用消息降级为 lowPriorityWarning。
  • 如果我们尝试进行水合,但没有可进行水合的 DOM 时,则发出警告。

这意味着当根元素存在时,ReactDOM.render() 仍会尝试水合,但可能不适用于新功能(例如顶级字符串或 fragments)。而 ReactDOM.hydrate() 总是会水合。

服务端渲染 与 客户端渲染区别

在服务端渲染中有别于客户端渲染的是,node服务会在后台先根据匹配到的路由生成完整的HTML字符串,然后再将HTML字符串发送到浏览器端,最终生成的HTML结构简化后如下:

<body>
    <div id="root">
        <div data-reactroot=""></div>
    </div>
</body>

在客户端渲染中是没有 data-reactroot 属性的,因此就可以区分出客户端渲染和服务端渲染。