React服务端渲染 (Server-Side Rendering, SSR) 是指在服务端实现页面渲染的技术。相对于客户端渲染(CSR),SSR有着更好的首屏渲染性能、更好的搜索引擎优化(SEO)和更好的社交分享体验,因此在实际项目中使用越来越广泛。
客户端渲染的问题
在客户端渲染模式下,首先浏览器请求到HTML,然后请求到JavaScript文件,随后JavaScript文件加载完毕后,需要渲染 React 组件,最终才能将已经渲染的React组件挂载到页面上。然而,这样的渲染模式会存在一些问题:
- 传输内容过多:页面会传输一个HTML文件和一个JavaScript文件,并且JavaScript文件需要等到加载完才能展示。这样就会出现内容较少的页面,而加载和渲染时间很长的状况。
- 首屏渲染较慢:因为客户端渲染需要等到JavaScript加载完成后再进行渲染操作,所以相对来说,页面首屏渲染比较慢,特别是对于网络比较差的用户。
- SEO不友好:搜索引擎爬虫一般只对普通的HTML文档进行解析,而由JavaScript生成的页面则不会进行解析。也就是说,在客户端渲染模式下,由JavaScript生成的页面无法被搜索引擎收录,会造成SEO的不利影响。
- 社交分享无法正常展示:与搜索引擎不同,对于社交媒体来说,页面链接预览的图片、文字等数据是根据HTML文档来生成,而不关心JavaScript生成的页面内容。因此,如果页面是由JavaScript生成的,则社交媒体预览时无法正常展示。
React服务端渲染的好处
React服务端渲染采用将所调用的React组件在服务端渲染后生成HTML,首屏渲染的时候就返回生成的HTML到浏览器。这就避免了客户端渲染的问题,具体好处如下:
- 传输内容减少:浏览器只需要请求HTML,服务端已经将React组件渲染成了标准的HTML,因此减少了请求量,节省的是客户端与服务端之间传输的数据量。
- 首屏渲染快:由于React组件在服务端就已渲染好了,因此能够避免等待JavaScript加载完毕后再进行渲染操作的状况,能够大大减少页面首次渲染的时间,提升用户体验。
- SEO更加友好:由于服务端已经将React组件渲染成了标准的HTML文档,能够被搜索引擎进行索检,因此提升了网站的SEO排名。
- 社交分享无误展示:由于服务端渲染,社交媒体等应用也能够正常预览并展示网站的信息。
SSR 原理
React服务端渲染,就是将React组件在服务端渲染成HTML,而生成HTML的过程可以概括为以下五个步骤:
1.通过请求获取到需要渲染的React组件。
2.创建根组件,并用 Rendering Context 管理生成的 HTML。
3.使用 ReactDOM/server 渲染器,为每一个需要渲染的组件生成 HTML 。
4.根据 Rendering Context 的 HTML 生成结果,生成完整的 HTML 文档。
5.用生成的 HTML 响应请求。
具体的实现可以参考下面的实例:
1. 路由配置
文章详情页会根据URL参数来获取对应的文章数据,并通过props传递给组件显示。因此需要路由配置来绑定url和React组件。
import { BrowserRouter, Route } from 'react-router-dom'
import Article from './Article'
const App = () => (
<BrowserRouter>
<Route path="/" component={Article} />
<Route path="/article/:id" component={Article} />
</BrowserRouter>
)
2. 获取文章数据
在服务端渲染过程中,需要从数据源获取对应数据,因此网络请求在这里就显得特别重要。可以使用 isomorphic-fetch这个库发起网络请求。
import fetch from 'isomorphic-fetch'
const getArticle = (id) => {
return fetch(`/api/article/${id}`).then(res => res.json())
}
3. 渲染React组件
在服务端渲染的时候,需要用 renderToString
方法将React组件渲染成HTML字符串。
import React from 'react'
import { renderToString } from 'react-dom/server'
import Article from './Article'
import Layout from './Layout'
const articleHandler = async (req, res) => {
const { id } = req.params
const data = await getArticle(id) // 获得数据
const renderedMarkup = renderToString(
<Article data={data} /> //习惯性传值方式
)
res.send(Layout(renderedMarkup, data)) // Layout 用来渲染 html 外壳
}
4. 完整的HTML生成
最终的HTML需要包含HTML的doctype、Head等信息。这部分的内容可以使用 react-helmet 生成。
import { renderToString } from 'react-dom/server'
import Layout from './Layout'
import Article from './Article'
import { Helmet } from 'react-helmet'
const articleHandler = async (req, res) => {
const { id } = req.params
const data = await getArticle(id) // 获得数据
const html = renderToString(<Article data={data} />)
const helmet = Helmet.renderStatic()
const fullHtml = Layout({
html,
title: data.title,
meta: helmet.meta.toString(),
link: helmet.link.toString(),
script: helmet.script.toString(),
})
res.send(fullHtml)
}
SSR 实践
使用 React 官方脚手架:Create React App 来构建一个包含 SSR 的 React 应用。
首先,安装 Create React App 脚手架
npm i -g create-react-app # 全局安装
create-react-app my-app --scripts-version=react-scripts-rewired
cd my-app
Create React App 默认是客户端渲染,需要改造成服务端渲染:
npm i express cors
接下来,在 src
目录下新建一个 server.js
文件,实现服务端渲染的逻辑。
import express from "express";
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import { StaticRouter } from 'react-router-dom';
import App from './App';
const app = express();
app.use(express.static('./build'));
app.get('/*', function (req, res) {
const context = {};
const markup = ReactDOMServer.renderToString(
<StaticRouter location={req.url} context={context}>
<App />
</StaticRouter>
);
if (context.url) {
res.redirect(301, context.url);
} else {
res.status(200).send(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<title>React App</title>
</head>
<body>
<div id="root">${markup}</div>
<script src="./static/js/main.js"></script>
</body>
</html>
`);
}
});
创建好服务端渲染的脚本后,可以用 npm run build
命令来打包Server端代码。
npm i -g serve # 安装 serve 静态文件服务器
npm run build # 客户端代码和服务端代码都打包到 ./build 目录中
serve -s build # 启动静态文件服务器
上面的内容就是我们的服务端渲染的核心处理步骤,为了最大效果,我们还需要加上上面两个用户故事里的 SEO 和 社交分享的需求解决。为此我们在客户端和服务器端都使用 react-helmet 解决SEO等问题。
npm i react-helmet
然后,在服务器端可能的每个 URL 访问之前,我们都请求数据控制器获取必要数据。
const fetchData = async () => {
const res = await fetch(`/api/search?query=${q}`);
const data = await res.json();
return data;
};
app.get('/:query', async (req, res) => {
const data = await fetchData(req.params.query);
const helmet = Helmet.renderStatic();
const markup = ReactDOMServer.renderToString(
<StaticRouter location={req.url}>
<App data={data} />
</StaticRouter>
);
res.send(template(helmet, markup, data));
});
这里演示一个简单的服务端渲染的例子搜索结果页面
的服务端渲染:
import { renderToString } from "react-dom/server";
import React from "react";
import { StaticRouter } from "react-router-dom";
import { Helmet } from "react-helmet";
import App from "../src/App";
const path = require("path");
const fs = require("fs");
// 读取模板文件 html
const HTML_TEMPLATE = fs.readFileSync(
path.resolve(__dirname, "../build/index.html"),
"utf8"
);
const helmet = Helmet.renderStatic();
const content = renderToString(
<StaticRouter location={req.url} context={{}}>
<App />
</StaticRouter>
);
const html = HTML_TEMPLATE.replace(
'<div id="root"></div>',
`<div id="root">${content}</div>
${helmet.meta.toString()}
${helmet.title.toString()}
${helmet.link.toString()}
${helmet.script.toString()}
`
);
res.send(html);
}
通过上述方式,我们就能够快速完成一个React服务端渲染的项目。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:React服务端渲染原理解析与实践 - Python技术站