有兴趣阅读我们的下一本书吗?了解更多关于使用 React 构建大型 JavaScript Web 应用程序

渲染模式

服务器端渲染

服务器端渲染 (SSR) 是渲染网页内容的最古老方法之一。 SSR 会生成页面的完整 HTML,以响应用户的请求进行渲染。 内容可能包含来自数据存储或外部 API 的数据。

连接和获取操作在服务器端处理。 用于格式化内容的 HTML 也会在服务器端生成。 因此,使用 SSR,我们可以避免为数据获取和模板化进行额外的往返。 因此,客户端不需要渲染代码,也不需要将与之对应的 JavaScript 发送到客户端。

使用 SSR,每个请求都会被独立处理,并被服务器视为一个新的请求。 即使两个连续请求的输出差别不大,服务器也会从头开始处理和生成它。 由于服务器对多个用户都是通用的,因此处理能力由所有在给定时间处于活动状态的用户共享。


经典 SSR 实现

让我们看看如何使用经典 SSR 和 JavaScript 创建一个页面来显示当前时间。

index.html
index.js
1<!DOCTYPE html>
2<html>
3 <head>
4 <title>Time</title>
5 </head>
6 <body>
7 <div>
8 <h1>Hello, world!</h1>
9 <b>It is <div id=currentTime></div></b>
10 </div>
11 </body>
12</html>

注意,这与提供相同输出的 CSR 代码不同。 还需要注意的是,虽然 HTML 由服务器渲染,但此处显示的时间是客户端的本地时间,由 JavaScript 函数 tick() 填充。 如果你想显示任何其他特定于服务器的数据,例如服务器时间,你需要在渲染 HTML 之前将其嵌入其中。 这意味着它不会在没有往返服务器的情况下自动刷新。


优点和缺点

在服务器上执行渲染代码并减少 JavaScript 可以提供以下优势。

更少的 JavaScript 导致更快的 FCP 和 TTI

在页面上有多个 UI 元素和应用程序逻辑的情况下,与 CSR 相比,SSR 的 JavaScript 明显更少。 因此,加载和处理脚本所需的时间更少。 FP、FCP 和 TTI 更短,并且 FCP = TTI。 使用 SSR,用户无需等待所有屏幕元素出现并变为可交互状态。

图片源:https://developers.google.com/web/updates/2019/02/rendering-on-the-web

为客户端 JavaScript 提供额外的预算

开发团队需要使用 JS 预算来限制页面上的 JS 数量,以实现所需的性能。 使用 SSR,由于你直接消除了渲染页面所需的 JS,因此它为应用程序可能需要的任何第三方 JS 创建了额外的空间。

支持 SEO

搜索引擎爬虫能够轻松地抓取 SSR 应用程序的内容,从而确保页面上的 SEO 更高。

由于上述优势,SSR 非常适合静态内容。 但是,它也有一些缺点,因此它并不适合所有场景。

缓慢的 TTFB

由于所有处理都在服务器端进行,因此在以下一种或多种情况下,服务器的响应可能会延迟

  • 多个同时用户导致服务器负载过重。
  • 网络速度慢
  • 服务器代码未优化。

一些交互需要完全重新加载页面

由于客户端没有所有代码,因此需要频繁地往返服务器以完成所有关键操作,导致页面完全重新加载。 这可能会增加交互之间的时间,因为用户需要在操作之间等待更长时间。 因此,使用 SSR 无法实现单页应用程序。

为了解决这些缺点,现代框架和库允许在同一个应用程序中对服务器和客户端进行渲染。 我们将在后面的章节中详细介绍这些内容。 首先,让我们看一下使用 Next.js 的更简单的 SSR 形式。


使用 Next.js 的 SSR

Next.js 框架也支持 SSR。 这会在每个请求时预先在服务器上渲染页面。 可以通过从页面中导出一个名为 getServerSideProps() 的异步函数来实现。

export async function getServerSideProps(context) {
  return {
    props: {}, // will be passed to the page component as props
  };
}

context 对象包含 HTTP 请求和响应对象、路由参数、查询字符串、语言环境等的键。

以下实现展示了 getServerSideProps() 用于在使用 React 格式化的页面上渲染数据。 完整的实现可以在 此处 找到。

users/page.jsx
1// data fetched from an external data source using `getServerSideProps`
2
3const Users = ({ users, error }) => {
4 return (
5 <section>
6 <header>
7 <h1>List of users</h1>
8 </header>
9 {error && <div>There was an error.</div>}
10 {!error && users && (
11 <table>
12 <thead>
13 <tr>
14 <th>Username</th>
15 <th>Email</th>
16 <th>Name</th>
17 </tr>
18 </thead>
19 <tbody>
20 {users.map((user, key) => (
21 <tr key={key}>
22 <td>{user.username}</td>
23 <td>{user.email}</td>
24 <td>{user.name}</td>
25 </tr>
26 ))}
27 </tbody>
28 </table>
29 )}
30 </section>
31 );
32};
33
34export async function getServerSideProps() {
35 // Fetch data from external API
36 const res = await fetch("https://jsonplaceholder.typicode.com/users")
37 const data = await res.json();
38
39 // Pass data to the page via props
40 return { props: { data } }
41}
42
43export default Users;

React for the Server

React 可以进行同构渲染,这意味着它可以在浏览器和其他平台(如服务器)上运行。 因此,UI 元素可以使用 React 在服务器端渲染。

React 也可以与通用代码一起使用,通用代码允许相同的代码在多个环境中运行。 这是通过在服务器上使用 Node.js 或称为 Node 服务器来实现的。 因此,通用 JavaScript 可以用来在服务器端获取数据,然后使用同构 React 进行渲染。

让我们看看使这一切成为可能的 React 函数。

ReactDOMServer.renderToString(element);

此函数返回一个与 React 元素相对应的 HTML 字符串。 然后可以将 HTML 渲染到客户端,以便更快地加载页面。

renderToString() 函数可以与 ReactDOM.hydrate() 一起使用。 这将确保渲染的 HTML 在客户端上被原样保留,并且只有事件处理程序在加载后附加。

为了实现这一点,我们在客户端和服务器上使用一个 .js 文件,对应于每个页面。 服务器上的 .js 文件将渲染 HTML 内容,而客户端上的 .js 文件将对其进行水合。

假设你有一个名为 App 的 React 元素,其中包含要在通用 app.js 文件中定义的要渲染的 HTML。 服务器和客户端的 React 都可以识别 App 元素。

服务器上的 ipage.js 文件可以包含以下代码

app.get("/", (req, res) => {
  const app = ReactDOMServer.renderToString(<App />);
});

现在,可以使用常量 App 来生成要渲染的 HTML。 客户端的 ipage.js 将包含以下内容,以确保元素 App 被水合。

ReactDOM.hydrate(<App />, document.getElementById("root"));

有关使用 React 进行 SSR 的完整示例,请参见 此处