React 16 中的 hydrate() 和 render() 有什么区别?

What's the difference between hydrate() and render() in React 16?

我已经阅读了文档,但我并没有真正理解 React 16 中 hydrate()render() 之间的区别。

我知道hydrate()是用来结合SSR和客户端渲染的

谁能解释一下什么是保湿,然后 ReactDOM 有什么区别?

来自 ReactDOMServer 文档(强调我的):

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.

粗体文字是主要区别。如果初始 DOM 和当前 DOM 之间存在差异,render 可能会更改您的节点。 hydrate 只会附加事件处理程序。

来自Github issue that introduced hydrate as a separate API

If this is your initial DOM:

<div id="container">
    <div class="spinner">Loading...</div>
</div>

and then call:

ReactDOM.render(
   <div class="myapp">
      <span>App</span>
   </div>,
   document.getElementById('container')
)

intending to do a client-side only render (not hydration). Then you end with

<div id="container">
   <div class="spinner">
       <span>App</span>
   </div>
</div>

Because we don't patch up the attributes.

仅供参考,他们没有修补属性的原因是

... This would be really slow to hydrate in the normal hydration mode and slow down initial render into a non-SSR tree.

除了以上...

ReactDOM.hydrate()render() 相同,但它用于 hydrate(附加事件侦听器)容器 其 HTML 内容是由 ReactDOMServer 渲染。 React 将尝试将事件侦听器附加到现有标记

使用 ReactDOM.render() 来混合服务器渲染的容器由于速度慢而被弃用,并且将在 React 17 中被删除,因此请改用 hydrate() .

Hydrate主要用于SSR(Server side Rendering)。 SSR 为您提供了从服务器发送的框架或 HTML 标记,这样当您的页面第一次加载时它不是空白的,搜索引擎机器人可以为 SEO 索引它(SSR 的一个用例)。因此 hydrate 将 JS 添加到您的页面或应用 SSR 的节点。以便您的页面响应用户执行的事件。

Render 用于在客户端浏览器上呈现组件另外,如果您尝试用 render 替换 hydrate,您将收到一条警告,指出 render 已被弃用并且不能在 SSR 的情况下使用。它被删除是因为它比水合物慢。

将功能放回已在服务器端 React 中呈现的 HTML 的整个过程称为水合作用。

因此,在渲染过的 HTML 上重新渲染的过程称为水合作用。

因此,如果我们尝试通过调用 ReactDOM.render() 来滋润我们的应用程序,它应该通过调用 ReactDOM.hydrate().

来完成

对于上面所说的 hydrate 的使用,我没有任何具体要补充的内容,但在尝试了解它的过程中,我举了一个小例子,所以这是为找到的人准备的工作很有帮助。

目标

提供两个页面,一个使用 ReactDOM.hydrate,一个使用 ReactDOM.render。它们将依赖于一些用 JSX 编写的 React 组件,这些组件由 <script> 标签加载,人工延迟(由服务器)来说明 hydraterender 之间的区别。

基本结构

  1. 一个文件具有 HTML "skeleton"
  2. 一个文件,其中包含用 JSX 编写的自定义 React 组件
  3. 一个脚本生成所有页面供服务器使用
  4. 一个脚本到 运行 服务器

结果

生成页面和 运行 服务器后,我转到 127.0.0.1 并看到 header hydrate,一个按钮和两个 link。我可以点击按钮,但没有任何反应。片刻之后,文档完成加载,按钮开始计算我的点击次数。然后我点击 "render" link。现在,我看到的页面有 header render 和两个 link,但没有按钮。片刻之后,按钮出现并立即响应。

说明

在 "hydrate" 页面上,所有标记都会立即呈现,因为页面提供了所有必要的 html。该按钮没有响应,因为还没有连接任何回调。 components.js 完成加载后,load 事件从 window 触发,回调与 hydrate.

连接

在 "render" 页面上,按钮标记不随页面提供,而仅由 ReactDOM.render 注入,因此不会立即可见。请注意页面的外观是如何被最终加载的脚本明显改变的。

来源

这是我正在使用的自定义反应组件。它将被节点中的服务器使用,对静态渲染组件做出反应,并且还将从服务器动态加载以用于页面(这是检查 exportsReact objects 在文件的开头)。

// components.jsx

var exports = typeof(exports) == 'object' ? exports : {};
var React = typeof(React) == 'object' ? React : require('react');

function MyButton(props) {
  [click, setClick] = React.useState(0);
  function handleClick() { setClick(click + 1); }
  return (
    <button onClick={handleClick}>Clicked: {click}</button>
  );
}

exports.MyButton = MyButton;

这是用于生成服务器所需的所有页面的脚本。首先,babel 用于将 components.jsx 转换为 javascript,然后使用这些组件以及 React 和 ReactDOMServer 来创建实际页面。这些页面是使用从文件 pageTemplate.js 导出的函数 getPage 创建的,如下所示。

// genScript.js

let babel          = require('@babel/core');
let fs             = require('fs');
let ReactDOMServer = require('react-dom/server');
let React          = require('react');
let pageTemplate   = require('./pageTemplate.js');

script = babel.transformFileSync(
  'components.jsx', 
  {presets : [['@babel/react']]}
);

fs.writeFileSync('components.js',script.code);
let components = require('./components.js');

hydrateHTML = pageTemplate.getPage(
  'MyButton',
  ReactDOMServer.renderToString(React.createElement(components.MyButton)),
  'hydrate'
);

renderHTML = pageTemplate.getPage(
  'MyButton',
  '',
  'render'
);

fs.writeFileSync('hydrate.html',hydrateHTML);
fs.writeFileSync('render.html',renderHTML);

这个文件只是导出前面提到的getPage函数。

// pageTemplate.js

exports.getPage = function(
  reactElementTag,
  reactElementString,
  reactDOMMethod
  ) {
  return `
  <!DOCTYPE html>
  <html>
    <head>
      <meta charset="utf-8" />
      <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js" defer></script>
      <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" defer></script>
      <script src="./components.js" defer></script>
    </head>
    <body> 
      <h1>${ reactDOMMethod }</h1>
      <div id="react-root">${ reactElementString }</div> 
      <a href="hydrate.html">hydrate</a>
      <a href="render.html">render</a>
    </body>
    <script>
      window.addEventListener('load', (e) => {
        ReactDOM.${ reactDOMMethod }(
          React.createElement(${ reactElementTag }),
          document.getElementById('react-root')
        );
      });
    </script>
  </html>
  `;
}

最后,实际服务器

// server.js

let http = require('http');
let fs   = require('fs');

let renderPage       = fs.readFileSync('render.html');
let hydratePage      = fs.readFileSync('hydrate.html');
let componentsSource = fs.readFileSync('components.js');

http.createServer((req, res) => {
  if (req.url == '/components.js') {
    // artificial delay
    setTimeout(() => {
    res.setHeader('Content-Type','text/javascript');
    res.end(componentsSource);
    }, 2000);
  } else if (req.url == '/render.html') {
    res.end(renderPage);
  } else {
    res.end(hydratePage);
  }
}).listen(80,'127.0.0.1');

render 将清除指定元素中的所有内容(在大多数情况下命名为 'root')并重建它,而 hydrate 将保留指定元素中已经存在的所有内容并从中构建,制作初始页面加载速度更快。

hydrate() 当我们想要在服务器端呈现 React 应用程序并在客户端水化 JavaScript 包时使用,这使我们的应用程序速度更快,也允许搜索引擎抓取您的网页用于 SEO 目的。

但是当我们将渲染方法更改为水合物时会发生什么

该错误清楚地表明,如果您的应用程序未使用服务器端渲染(SSR),请使用reactdom render 启动客户端渲染。 Read this article