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

2022-08-30 05:25:08

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

我知道用于组合 SSR 和客户端呈现。hydrate()

有人可以解释什么是保湿,然后ReactDOM有什么区别吗?


答案 1

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

如果你调用一个已经具有这个服务器渲染标记的节点,React 将保留它并只附加事件处理程序,从而让你拥有非常高性能的首次加载体验。ReactDOM.hydrate()

粗体文本是主要区别。 如果初始 DOM 和当前 DOM 之间存在差异,则可能会更改节点。 将仅附加事件处理程序。renderhydrate

从将水合物作为单独的API引入的Github问题中

如果这是您的初始 DOM:

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

然后呼叫:

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

打算只做客户端渲染(而不是水合作用)。然后你以

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

因为我们不修补属性。

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

...这在正常水合模式下补水的速度非常慢,并且会减慢初始渲染到非 SSR 树中的速度。


答案 2

我没有任何具体的东西可以补充上面所说的关于使用,但是在尝试了解它时,我整理了一个小例子,所以这是任何发现它有帮助的人的工作。hydrate

目标

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

基本结构

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

结果

在我生成页面并运行服务器后,我转到并显示标题水合物,一个按钮和两个链接。我可以单击该按钮,但没有任何反应。片刻之后,文档完成加载,按钮开始计算我的点击次数。然后我点击“渲染”链接。现在,我看到的页面具有标题呈现和两个链接,但没有按钮。片刻之后,按钮出现并立即响应。127.0.0.1

解释

在“hydrate”页面上,所有标记都会立即呈现,因为所有必要的html都随页面一起提供。该按钮无响应,因为尚未连接任何回调。完成加载后,事件将从 中触发,并且回调与 连接。components.jsloadwindowhydrate

在“呈现”页面上,按钮标记不随页面一起提供,而仅由 注入,因此它不会立即可见。请注意,最终加载的脚本如何不和谐地更改页面的外观。ReactDOM.render

这是我正在使用的自定义反应组件。它将由节点中的服务器使用,以响应静态呈现组件,并且它还将从服务器动态加载以用于页面(这是在文件开头检查和对象的目的)。exportsReact

// 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用于将consents.jsx转换为javascript,然后使用这些组件以及React和ReactDOMServer来创建实际的页面。这些页面是使用从文件导出的功能创建的,如下所示。getPagepageTemplate.js

// 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');