Skip to content

SSR 和 Hydration

¥SSR and Hydration

服务器端渲染 (SSR)

¥Server-side Rendering (SSR)

服务器端渲染 (SSR) 是一种技术,可帮助我们将组件渲染为服务器上的 HTML 字符串,将它们直接发送到浏览器,最后将静态标记 "hydrate" 到客户端上完全交互式的应用。

¥Server-side Rendering (SSR) is a technique that helps us render our components into HTML strings on the server, send them directly to the browser, and finally "hydrate" the static markup into a fully interactive app on the client.

React

假设我们想使用 React 渲染无状态应用。为了做到这一点,我们需要使用 expressreactreact-dom/server。我们不需要 react-dom/client,因为它是一个无状态应用。

¥Let's say we want to render a stateless app using React. In order to do that, we need to use express, react and react-dom/server. We don't need react-dom/client since it's a stateless app.

让我们深入研究一下:

¥Let's dive into that:

  • express 帮助我们构建一个可以使用 Node 运行的 Web 应用,

    ¥express helps us build a web app that we can run using Node,

  • react 帮助我们构建我们在应用中使用的 UI 组件,

    ¥react helps us build the UI components that we use in our app,

  • react-dom/server 帮助我们在服务器上渲染我们的组件。

    ¥react-dom/server helps us render our components on a server.

json
// tsconfig.json
{
  "compilerOptions": {
    "noImplicitAny": false,
    "noEmitOnError": true,
    "removeComments": false,
    "sourceMap": true,
    "target": "esnext"
  },
  "include": ["**/*"]
}

注意:不要忘记从你的 tsconfig.json 文件中删除所有注释。

¥Note: do not forget to remove all comments from your tsconfig.json file.

tsx
// app.tsx
export const App = () => {
  return (
    <html>
      <head>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <title>Static Server-side-rendered App</title>
      </head>
      <body>
        <div>Hello World!</div>
      </body>
    </html>
  )
}
tsx
// server.tsx
import express from 'express'
import React from 'react'
import ReactDOMServer from 'react-dom/server'

import { App } from './app.tsx'

const port = Number.parseInt(process.env.PORT || '3000', 10)
const app = express()

app.get('/', (_, res) => {
  const { pipe } = ReactDOMServer.renderToPipeableStream(<App />, {
    onShellReady() {
      res.setHeader('content-type', 'text/html')
      pipe(res)
    },
  })
})

app.listen(port, () => {
  console.log(`Server is listening at ${port}`)
})
sh
tsc --build
sh
node server.js

补水

¥Hydration

Hydration 将来自服务器的初始 HTML 快照转换为在浏览器中运行的完全交互式应用。将组件 "hydrate" 化的正确方法是使用 hydrateRoot

¥Hydration turns the initial HTML snapshot from the server into a fully interactive app that runs in the browser. The right way to "hydrate" a component is by using hydrateRoot.

React

假设我们想使用 React 渲染有状态应用。为了做到这一点,我们需要使用 expressreactreact-dom/serverreact-dom/client

¥Let's say we want to render a stateful app using React. In order to do that we need to use express, react, react-dom/server and react-dom/client.

让我们深入研究一下:

¥Let's dive into that:

  • express 帮助我们构建一个可以使用 Node 运行的 Web 应用,

    ¥express helps us build a web app that we can run using Node,

  • react 帮助我们构建我们在应用中使用的 UI 组件,

    ¥react helps us build the UI components that we use in our app,

  • react-dom/server 帮助我们在服务器上渲染我们的组件,

    ¥react-dom/server helps us render our components on a server,

  • react-dom/client 帮助我们在客户端上水合我们的组件。

    ¥react-dom/client helps us hydrate our components on a client.

注意:不要忘记,即使我们可以在服务器上渲染组件,在客户端对它们进行 "hydrate" 操作以使其具有交互性也很重要。

¥Note: Do not forget that even if we can render our components on a server, it is important to "hydrate" them on a client to make them interactive.

json
// tsconfig.json
{
  "compilerOptions": {
    "noImplicitAny": false,
    "noEmitOnError": true,
    "removeComments": false,
    "sourceMap": true,
    "target": "esnext"
  },
  "include": ["**/*"]
}

注意:不要忘记删除你的 tsconfig.json 文件中的所有注释。

¥Note: do not forget to remove all comments in your tsconfig.json file.

tsx
// app.tsx
export const App = () => {
  return (
    <html>
      <head>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <title>Static Server-side-rendered App</title>
      </head>
      <body>
        <div>Hello World!</div>
      </body>
    </html>
  )
}
tsx
// main.tsx
import ReactDOMClient from 'react-dom/client'

import { App } from './app.tsx'

ReactDOMClient.hydrateRoot(document, <App />)
tsx
// server.tsx
import express from 'express'
import React from 'react'
import ReactDOMServer from 'react-dom/server'

import { App } from './app.tsx'

const port = Number.parseInt(process.env.PORT || '3000', 10)
const app = express()

app.use('/', (_, res) => {
  const { pipe } = ReactDOMServer.renderToPipeableStream(<App />, {
    bootstrapScripts: ['/main.js'],
    onShellReady() {
      res.setHeader('content-type', 'text/html')
      pipe(res)
    },
  })
})

app.listen(port, () => {
  console.log(`Server is listening at ${port}`)
})
sh
tsc --build
sh
node server.js

警告:你传递给 hydrateRoot 的 React 树需要产生与服务器上相同的输出。导致水合错误的最常见原因包括:

¥Warning: The React tree you pass to hydrateRoot needs to produce the same output as it did on the server. The most common causes leading to hydration errors include:

  • 根节点内 React 生成的 HTML 周围有额外的空格(如换行符)。

    ¥Extra whitespace (like newlines) around the React-generated HTML inside the root node.

  • 在你的渲染逻辑中使用类似 typeof window !== 'undefined' 的检查。

    ¥Using checks like typeof window !== 'undefined' in your rendering logic.

  • 在渲染逻辑中使用仅浏览器 API(如 window.matchMedia)。

    ¥Using browser-only APIs like window.matchMedia in your rendering logic.

  • 在服务器和客户端上渲染不同的数据。

    ¥Rendering different data on the server and the client.

React 从一些水化错误中恢复,但你必须像修复其他错误一样修复它们。在最好的情况下,它们会导致速度变慢;在最坏的情况下,事件处理程序可能会附加到错误的元素。

¥React recovers from some hydration errors, but you must fix them like other bugs. In the best case, they’ll lead to a slowdown; in the worst case, event handlers can get attached to the wrong elements.

你可以在此处阅读有关注意事项和陷阱的更多信息:hydrateRoot

¥You can read more about the caveats and pitfalls here: hydrateRoot

Zustand v5.0 中文网 - 粤ICP备13048890号