「React源码分析」二. React Renderer渲染器react-dom包概览与其主要API的功能及其作用

一. 概览

react-dom 包作为 React 的 DOM 模型渲染器和服务器渲染器的入口点。它旨在与通用 React 包配对,后者作为 react 提供给 npm。也就是说,react 的渲染过程是使用 react-dom 中提供的一系列API进行的。

react-dom 的 package 提供了可在应用顶层使用的 DOM(DOM-specific)方法,我们在有需要的情况下可以把这些方法用于 React 模型以外的地方。不过一般情况下,大部分组件都不需要使用这个模块。

二. 安装和使用

使用npm命令安装 react 和 react-dom :

npm install react react-dom

编写以下代码使 react 和 react-dom 搭配使用,便可以在浏览器中渲染出来:

var React = require('react');
var ReactDOM = require('react-dom');

function MyComponent() {
  return <div>Hello World</div>;
}

ReactDOM.render(<MyComponent />, node);

ReactDOM中提供了一系列的API,以供React在不同的时机对React维护的虚拟DOM树进行操作并与浏览器DOM进行同步。

三. API简介

首先结合packages\react-dom\index.stable.js中对React-DOM的API定义,我们可以得到主要的 API 方法:

/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 */

export {
  __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
  createPortal,
  createRoot,
  hydrateRoot,
  findDOMNode,
  flushSync,
  hydrate,
  render,
  unmountComponentAtNode,
  unstable_batchedUpdates,
  unstable_renderSubtreeIntoContainer,
  version,
} from './src/client/ReactDOM';

我们可以看到目前react-dom中稳定的API有9个。下面分别对这 9 个API的功能及作用进行概括性地分析。

1. render

ReactDOM.render(
  element: React$Element<any>,
  container: Container,
  callback: ?Function,
)

在提供的 container 里渲染一个 React 元素,并返回对该组件的引用(或者针对无状态组件返回 null)。

如果 React 元素之前已经在 container 里渲染过,这将会对其执行更新操作,并仅会在必要时改变 DOM 以映射最新的 React 元素。

如果提供了可选的回调函数,该回调将在组件被渲染或更新之后被执行。

2. hydrate

ReactDOM.hydrate(
  element: React$Node,
  container: Container,
  callback: ?Function,
)

与 render() 相同,但它用于在 ReactDOMServer 渲染的容器中对 HTML 的内容进行 hydrate 操作。React 会尝试在已有标记上绑定事件监听器。

换而言之,hydrate是服务端渲染方式 (SSR) 上的render()方法。 SSR会在服务端预先渲染好React页面的所有内容,形成最终的HTML页面然后返回给浏览器,因此,浏览器在第一次在页面加载时不为空白,这样搜索引擎可以将其索引为SEO。 hydrate会将JS添加到页面或要应用SSR的节点。 这样页面才能响应用户执行的事件。

3. unmountComponentAtNode

ReactDOM.unmountComponentAtNode(container: Container): Boolean

从 DOM 中卸载组件,会将其事件处理器(event handlers)和 state 一并清除。如果指定容器上没有对应已挂载的组件,这个函数什么也不会做。如果组件被移除将会返回 true,如果没有组件可被移除将会返回 false。

4. findDOMNode

ReactDOM.findDOMNode(
  componentOrElement: Element | ?React$Component<any, any>,
): null | Element | Text

如果组件已经被挂载到 DOM 上,此方法会返回浏览器中相应的原生 DOM 元素。此方法对于从 DOM 中读取值很有用,例如获取表单字段的值或者执行 DOM 检测(performing DOM measurements)。但大多数情况下,可以绑定一个 ref 到 DOM 节点上,可以完全避免使用 findDOMNode。

当组件渲染的内容为 null 或 false 时,findDOMNode 也会返回 null。当组件渲染的是字符串时,findDOMNode 返回的是字符串对应的 DOM 节点。从 React 16 开始,组件可能会返回有多个子节点的 fragment,在这种情况下,findDOMNode 会返回第一个非空子节点对应的 DOM 节点。

这个方法不被React官方所推荐使用,并在将来会被弃用 (deprecated) .

5. createPortal

ReactDOM.createPortal(
  children: ReactNodeList,
  container: Container,
  key: ?string = null,
): React$Portal

创建 portal。Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案。一个 portal 的典型用例是当父组件有 overflow: hidden 或 z-index 样式时,但你需要子组件能够在视觉上“跳出”其容器。例如,对话框、悬浮卡以及提示框。

6. createRoot

ReactDOM.createRoot(
  container: Container,
  options?: CreateRootOptions,
): RootType

创建一个React-DOM的root节点,返回这个root节点的引用。

7. hydrateRoot

ReactDOM.hydrateRoot(
  container: Container,
  initialChildren: ReactNodeList,
  options?: HydrateRootOptions,
): RootType

与createRoot()相同,但它适用于服务端渲染方式 (SSR) 。

8. flushSync

ReactDOM.flushSync(fn)

同步React-DOM与浏览器真实DOM。一般来说,flushSync是在React组件的生命周期中从内部自动调用的,当React已经处于渲染完成的状态时,手动调用flushSync()不会产生任何作用。同时,这个方法与组件中注册的副作用密切相关。

9. version

当前的版本号。它作为占位符存在,以便 DevTools 可以支持版本之间的工作标签更改。

四. 后续分析计划

首先将从React的基本工作流程和生命周期对React-DOM的API调用过程进行分析,深入分析每一步的具体操作代码逻辑。预计将使用大约 6 博客来循序渐进地完成这部分的分析工作。随后再对React的事件系统(Event System)的源码部分进行分析,重点分析合成事件的主要流程,预计将使用 4 篇左右的博客完成事件系统部分的源码分析工作。

发表评论