面试官:说说React服务端渲染怎么做?原理是什么?
Opened this issue · 0 comments
linwu-hi commented
服务端渲染(SSR)
服务端渲染简介
服务端渲染(SSR)是一种用于处理页面的技术,它在服务器端构建HTML结构并将完整的交互式页面发送到浏览器,然后将状态和事件绑定到该页面。SSR主要解决了两个关键问题:
- 搜索引擎优化(SEO):搜索引擎爬虫可以直接查看完全渲染的页面,因此SSR有助于提高网站的搜索引擎排名。
- 加速首屏加载:SSR可以减少首屏白屏时间,提升用户体验。
服务端渲染的问题
SEO问题
搜索引擎爬虫可以直接查看完全渲染的页面,而客户端渲染(Client-Side Rendering,简称CSR)可能会导致搜索引擎无法获取页面内容。
首屏加载问题
CSR可能导致首屏白屏,用户体验较差。SSR可以在服务器端渲染首屏,提高加载速度。
实现服务端渲染(SSR)
创建Express服务器
我们通过Express创建一个服务器,用于监听3000端口的请求,并在根目录请求时返回HTML页面。
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send(`
<html>
<head>
<title>SSR Demo</title>
</head>
<body>
Hello world
</body>
</html>
`);
});
app.listen(3000, () => console.log('Example app listening on port 3000!'));
我们将在服务器端编写React代码,并在app.js
中引用它。
使用Webpack进行打包
为了使服务器能够识别JSX,我们需要使用Webpack对项目进行打包转换。创建一个名为
webpack.server.js
的配置文件,并进行相关配置。
const path = require('path');
const nodeExternals = require('webpack-node-externals');
module.exports = {
target: 'node',
mode: 'development',
entry: './app.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'build'),
},
externals: [nodeExternals()],
module: {
rules: [
{
test: /\.js?$/,
loader: 'babel-loader',
exclude: /node_modules/,
options: {
presets: ['react', 'stage-0', ['env', { targets: { browsers: ['last 2 versions'] } }]],
},
},
],
},
};
使用renderToString
渲染React组件
我们借助
react-dom
提供的renderToString
方法,将React组件渲染为HTML字符串。
import express from 'express';
import React from 'react';
import { renderToString } from 'react-dom/server';
import Home from './src/containers/Home';
const app = express();
const content = renderToString(<Home />);
app.get('/', (req, res) => {
res.send(`
<html>
<head>
<title>SSR Demo</title>
</head>
<body>
${content}
</body>
</html>
`);
});
app.listen(3001, () => console.log('Example app listening on port 3001!'));
实现服务端和客户端共用一套代码
同构概念
同构是指一套React代码在服务器端和客户端都能运行。在同构中,服务端渲染完成页面结构,而浏览器端渲染完成事件绑定和一些交互。这种方式既可以提高首屏加载速度,又能保持页面的交互性。
处理路由问题
在React应用中,通常会存在多个页面和路由的情况。为了在服务端渲染中处理路由,需要配置路由信息。
:
import React from 'react';
import { Route } from 'react-router-dom';
import Home from './containers/Home';
export default (
<div>
<Route path="/" exact component={Home}></Route>
</div>
);
然后,可以通过index.js
引用路由信息:
import React from 'react';
import ReactDom from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import Router from '../Routers';
const App = () => {
return (
<BrowserRouter>
{Router}
</BrowserRouter>
);
};
ReactDom.hydrate(<App />, document.getElementById('root'));
解决路由渲染问题
在服务端渲染时,每个Route组件外面包裹着一层div,但在服务端返回的代码中并没有这个div。这可能会导致浏览器端渲染时出现问题。为了解决这个问题,我们需要在服务端执行一遍路由信息,使用
StaticRouter
来替代BrowserRouter
,并通过context
参数进行参数传递。
import express from 'express';
import React from 'react';
import { renderToString } from 'react-dom/server';
import { StaticRouter } from 'react-router-dom';
import Router from '../Routers';
const app = express();
app.use(express.static('public'));
app.get('/', (req, res) => {
const content = renderToString(
<StaticRouter location={req.path} context={{}}>
{Router}
</StaticRouter>
);
res.send(`
<html>
<head>
<title>SSR Demo</title>
</head>
<body>
<div id="root">${content}</div>
<script src="/index.js"></script>
</body>
</html>
`);
});
app.listen(3001, () => console.log('Example app listening on port 3001!'));
服务端渲染的工作流程
-
- 服务器(Node.js)接收客户端请求,并获取当前请求的URL路径。
-
- 服务器根据URL路径在已有的路由表中查找到对应的React组件,然后获取组件需要的数据。
-
- 服务器将数据作为
props
、context
或store
等形式传递给React组件。
- 服务器将数据作为
-
- 使用React内置的服务端渲染方法
renderToString()
将React组件渲染为HTML字符串。
- 使用React内置的服务端渲染方法
-
- 服务器在将最终的HTML响应发送给浏览器之前,将数据注入到HTML中。
-
- 浏览器开始渲染页面,执行节点对比,然后执行组件内事件绑定和交互。