客户端渲染和服务端渲染是什么?
客户端渲染
客户端渲染一般是单页面的方式来加载所有的资源文件,会被打包成js 文件和一些静态资源文件(如图片,css样式,一些字体等等)。一些HTML元素是需要通过JS加载和执行完之后,才会渲染的。比如在React和vue 单页面应用中,一些组件,需要等待整个框架加载完之后,才可以渲染。
服务端渲染
服务端渲染,是指服务端完成一部分的渲染工作。把HTML直接返回给客户端。
服务端渲染解决的问题
1、用于首屏加载速度过慢的问题,可以让用户的等待时间更短,体验感更好。
2、更好的搜索引擎优化。
3、减轻客户端负担,将部分的渲染工作,在服务端完成,可以减少浏览器压力。
4、避免客户端渲染的闪烁问题。
一些前置知识
import { renderToString } from 'react-dom/server'
- renderToString
renderToString将 React 树渲染为一个 HTML 字符串。
- ReactDOM.hydrate
浏览器通过 hydrate 把 dom 关联到 fiber 树,加上交互逻辑和再次渲染。
服务端渲染如何实现的?
1、使用StaticRouter 和renderToStirng 方法将对应的组件解析为HTML,然后加载客户端的client.js,最后通过服务端返回给用户。
如下代码所示,
let html = renderToString(
<Provider store={store}>
<StaticRouter context={context} location={req.path}>
{renderRoutes(routes)}
</StaticRouter>
</Provider>
)
res.send(`<html>
<head>
${helmet.title.toString()}
${helmet.meta.toString()}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" />
<style>${cssStr}</style>
</head>
<body>
<div id="root">${html}</div>
<script>
// 服务端:组件初始化时会请求数据,请求的数据会存到服务端仓库中,然后组件使用数据显示相应内容
// 客户端:为了避免组件挂载时又一次的请求数据(当服务器端已经请求过数据并返回了有数据的内容)
// 所以这里要获取下存在服务端仓库中的数据并作为初始值存到 window 中
// 俗称:数据的脱水
window.context = {
state:${JSON.stringify(store.getState())}
}
</script>
<script src="/client.js"></script>
</body>
</html>`)
2、使用ReactDOM.hydrate 方法,将服务端渲染未完成的工作完成,比如说绑定事件,继续交给客户端来做。
import routes from "../routes";
import { BrowserRouter, Route } from "react-router-dom";
import { Provider } from "react-redux";
import { getClientStore } from "../store";
import { renderRoutes, matchRoutes } from "react-router-config";
// hydrate 就是表示把服务器端渲染未完成的工作完成,比如说绑定事件
ReactDOM.hydrate(
<Provider store={getClientStore()}>
<BrowserRouter>{renderRoutes(routes)}</BrowserRouter>
</Provider>,
document.getElementById("root")
);
- 客户端和服务端分别配置webpack 进行打包。
- 客户端配置如下:
module.exports = merge(base, {
entry: './src/client/index.js',
output: {
path: path.resolve('public'),
filename: 'client.js'
},
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: true
}
}
]
}
]
}
})
- 服务端配置如下:
// 注意这个值
target: 'node',
entry: './src/server/index.js',
output: {
path: path.resolve('build'),
filename: 'server.js'
},
externals: [nodeExternal()],
module: {
rules: [
{
test: /\.css$/,
use: [
// 服务端渲染不能用 style-loader,因为 node 没有 document 对象,无法插入 style 标签
// 服务端本来就不能渲染 dom,只是提供 html/css/js 代码给浏览器,交给浏览器去渲染
// 服务端返回的 html 源码里,没有 style 标签
// 而在浏览器中的 html 源码里,有 style 标签,是通过 js 插入进去的
'isomorphic-style-loader',
{
loader: 'css-loader',
options: {
modules: true
}
}
]
}
]
}
})