React SSR简介
客户端渲染(CSR):服务端仅返回JSON数据,DATA和HTML在客户端渲染。
服务端渲染(SSR):服务端返回HTML,DATA和HTML在服务端进行渲染。
客户端渲染存在的问题:
1、首屏等待时间长,用户体验差。
客户端渲染过程: 服务端第一次对客户端响应请求,先只返回一个空的HTML文档,这个即index.html在这个文档中只包含一些css和js的外链。浏览器开始执行Html文档,然后又向服务端请求css和js外链文件。获取js 文件后,又开始向服务端请求页面的数据。等数据回来,然后等待js 把数据拼接好,然后此时时客户界面才会从空白到出现内容。
服务端渲染过程: 服务端先返回拼接好的html和数据,用户可以看到页面内容,虽然是静态的页面。接着发送js 请求,再解析js 的内容,最终,页面拥有动态的效果。且返回的是一个完整的html 的页面,利于搜索引擎的实现。
2、页面结构为空,不利于seo。
React SSR 的核心在于同构,让代码复用,实现客户端和服务端最大程度的代码复用。
初步渲染
1、引入要渲染的React组件 2、通过renderToString方法将React的组件转换为HTML字符串 3、将结果HTML字符串发送到客户端
webpack 的打包配置: Node环境不支持ESModule模块系统,不支持JSX 的语法。
为元素添加事件
开始只会返回html结构,而没有任何js代码,在客户端进第二次渲染,为组件元素添加事件。hydrate能够实现客户端二次渲染,复用原本已经存在和Dom节点,减少重新生成节点和删除原来节点的开销,通过react-dom 导入hydrate。
思路:1、在客户端进行使用hydrate进行二次渲染。
ReactDom.hydrate(<Home/>,document.getElementById("root"))
2、添加babel配置。
const path = require('path')
module.exports = {
mode:"development",
entry:"./src/client/index.js",
output:{
path:path.join(__dirname,"public"),
filename:"bundle.js"
},
module:{
rules:[
{
test:/\.js$/,
exclude:/node_modules/,
use:{
loader:"babel-loader",
options:{
presets:["@babel/preset-env","@babel/preset-react"]
}
}
}
]
}
}
3、在服务端返回的Html的结构中,添加
<html>
<head>
<title> React ssr </title>
</head>
<body>
<div id ="root"> ${content} </div>
<script src = "bundle.js"> </script>
</body>
</html>
4、服务端实现静态资源访问。
app.use(express.static("public"));
一些优化: 1、合并webpack:webpack-merge 2、合并项目启动命令:npm-run-all 3、服务器端大包体积优化:webpack-node-externals 4、将启动服务器的代码和渲染组件的代码进行区分
路由实现
服务端路由实现:
1、设置统一的路由规则,导出数组对象的路由形式。 2、使用renderRoute 将数组对象转变组件形式。 3、使用StaticRouter进行路由匹配
部分代码
import { renderToString } from 'react-dom/server'
import { renderRoutes } from "react-router-config"
import { StaticRouter } from "react-router-dom"
import routes from '../share/routes'
export default req => {
const content = renderToString(<StaticRouter location= {req.path}> {renderRoutes(routes)}</StaticRouter>)
return `
<html>
<head>
<title> React ssr </title>
</head>
<body>
<div id ="root"> ${content} </div>
<script src = "bundle.js"> </script>
</body>
</html>
`
}
客户端路由实现:
import React from "react"
import ReactDom from "react-dom"
import { BrowserRouter } from "react-router-dom"
import { renderRoutes } from 'react-router-config'
import routes from '../share/routes'
ReactDom.hydrate(<BrowserRouter>{renderRoutes(routes)}</BrowserRouter>,document.getElementById("root"))