1.ReactSSR相关观念回顾
1.1. 什么是客户端渲染(CSR)
CSR: Client Side Rendering
服务器端仅返回json数据就可以了,data(数据)和html的在客户端进行渲染。
数据和html 的拼接是在客户端完成的,是在浏览器当中使用javascript来完成的,对服务器端来说,只需要返回json数据就可以了。
1.2. 什么是服务端渲染(SSR)
SSR: Server Side Rendering
服务端返回html,data和html的拼接过程在服务器端进行渲染。
就是说当客户端向服务端发送请求,服务器端接受到这个请求以后,在服务器端获取到数据,并且把这个数据和html拼接好,最后再把拼接好的结果一起返回给客户端。客户端只需要把接收到html 显示出来就好了。
1.3.客户端渲染存在的问题:
- 首屏等待时间长,用户体验差
- 页面结构为空,不利于seo。
SPA应用中服务器端渲染解决的问题:
客户端渲染过程:
- 首先客户端向服务器端发送请求获取首页面,当服务端收到请求以后,它对客户端做出了响应,在这个响应的内容当中,只包含了一个空的html文档,在这个文档当中,只包含了一些css外链,和js外链,在这个请求的过程,用户是处于等待的状态的,在页面当中是没有任何内容的,接下来,浏览器要去执行这个空的html文档,在执行的过程当中,它又要去服务端发送请求,获取对应的外链文件,在获取外链文件的过程中,客户端还是处于等待的状态的,在页面当中仍然看不到内容,当客户端把外链文件加载完成之后,就会去执行对于的js文件,在执行js文件的过程中,又要向服务器端发送请求,获取当前这个页面所需要的数据,在请求数据的过程中,客户端依然什么都看不到,当数据请求完成之后,又要使用javascript把这个json数据和html拼接好,最终把拼接好的结果显示在页面中。直到最后一步,用户在浏览器中看到页面。
- 使用csr,可以看到大部分的时间用户都是处于等待的状态,所以用户体验不要。
这是存在的第一个问题,首屏等待时间长,用户体验差
。第2个问题,因为页面结构是空的,空的页面结构是不利于seo的,搜索引擎的爬虫工具在爬取页面内容的时候,由于你的页面结构为空,所以爬取不到任何的内容。
服务器端渲染的过程:
- 首先客户端向服务器端发送请求,服务器端收到请求之后,服务器端把数据和html拼接好,那么把拼接的结果返回给了客户端,请求的过程和响应的过程浏览器是处于等待的状态的。
- 接下来,浏览器去执行服务器端给它返回的结果,在响应的html文档当中,是包含了html与数据的,所以此时用户是能够看到这个页面内容的,只不过现在这个页面当中它只有静态的html内容,它并没有动态的效果,当浏览器去执行这个文档的时候,它又要去请求js文件了,当这个js文件请求成功以后,那么浏览器开始执行这个js文件,当浏览器把这个js文件执行完之后 ,在用户的界面中就有动态效果了。
- 通过ssr这个渲染的方法,用户早早的就看到了用户界面了,只不过这个html页面没有动态效果,只有我再去请求对应的js文件,执行了这个js文件之后,用户看到的才是一个完整的html 页面,但是不管怎么样,相对于客户端渲染来讲用户早早的看到了用户界面了。
所以解决了第一个问题:首屏等待时间长,用户体验差的问题
。那么对于服务器端来讲,返回的是一个完整的html页面,在我们查看网页的源代码的时候,是能够看到这个网站当中的内容的,所以当搜索引擎来爬取你的内容的时候,搜索引擎的爬虫工具也可以看到网站当中有什么内容,从而解决seo的问题
这是服务器端渲染的过程。
1.4 React SSR同构
同构指的是:代码复用,即实现客户端和服务端最大程度的代码复用。
在实现服务器端渲染的过程中,客户端渲染也是需要实现的,那么在实现的过程中,我们要实现客户端路由,服务器端路由,客户端redux和服务器端redux,那么在实现的过程中有很多的代码可以复用。他的核心在于代码的复用。
2.ReactSSR服务端渲染实战
2.1 项目初始化:
项目结构:
package.json
{
"name": "ssr-guide",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "npm-run-all --parallel dev:*",
"dev:server-run": "nodemon --watch build --exec \"node build/bundle.js\"",
"dev:server-build": "webpack --config webpack.server.js --watch",
"dev:client-build": "webpack --config webpack.client.js --watch"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@babel/cli": "^7.10.3",
"@babel/core": "^7.10.3",
"@babel/polyfill": "^7.10.4",
"@babel/preset-env": "^7.10.3",
"@babel/preset-react": "^7.10.1",
"axios": "^0.19.2",
"babel-loader": "^8.1.0",
"express": "^4.17.1",
"nodemon": "^2.0.4",
"npm-run-all": "^4.1.5",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-helmet": "^6.1.0",
"react-redux": "^7.2.0",
"react-router-config": "^5.1.1",
"react-router-dom": "^5.2.0",
"redux": "^4.0.5",
"redux-thunk": "^2.3.0",
"serialize-javascript": "^4.0.0",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0",
"webpack-merge": "^4.2.2",
"webpack-node-externals": "^1.7.2"
}
}
2.2 创建node服务器
创建:服务端 src/server/http.js
import express from 'express'
const app = express();
app.listen(3000, () => {
console.log('app is running 3000 port')
})
export default app
服务端入口文件:src/server/index.js
// 服务端入口文件
import React from 'react'
import app from './http'
import Home from '../share/pages/Home'
import {
renderToString } from 'react-dom/server'
// 接收来自客户端发送的请求
// 当客户端请求地址为:'/',时,服务器端做出的响应。
app.get('/' , (req, res) => {
// renderToString: 返回一个html字符串
const content = renderToString(<Home/>)
res.send(
`
<html>
<head>react ssr</head>
<body>
<div id="root">${
content}</div>
</body>
</html>
`
)
})
创建组件: src/share/Home.js
import React from 'react'
function Home () {
return <div>home works </div>
}
export default Home
实现ssr步骤:
- 引入要渲染的react组件
- 通过renderToString方法将react组件转换为html字符串
- 将结果html字符串响应到客户端。
renderToString 方法用于将react组件转换为html字符串,通过react-dom/server导入。
2.3 webpack打包配置
因为当前的代码在运行不起来的。因此需要打包。通过打包之后的代码,启动项目。原因有2 个: 1. node环境不支持 ESModule模块系统,不支持jsx语法。
node版本: v10.16.0
webpack.base.js
// @babel/preset-env:转换高级的js语法,
// @babel/preset-react:转换jsx语法
module.exports = {
mode: "development",
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: [
[
"@babel/preset-env",
{
useBuiltIns: "usage"
}
],
"@babel/preset-react"
]
}
}
}
]
}
};
创建服务端webpack配置:webpack.server.js
const path = require('path');
const merge = require('webpack-merge');
const baseConfig = require('./webpack.base');
const config = {
target: 'node',
entry: './src/server/index.js',
output: {
// path.join拼接路径
path: path.join(__dirname, 'build'),
filename: 'bundle.js'
},
}
module.exports = merge(baseConfig, config);
package.json
// --exec: 执行命令: \"node build/bundle.js\"
"scripts": {
// 执行node
"dev:server-run": "nodemon --watch build --exec \"node build/bundle.js\"",
// watch 监听文件的变化,当文件内容发生了变化自动去打包
"dev:server-build": "webpack --config webpack.server.js --watch"
},
执行:npm run dev:server-build
执行:npm run dev:server-run
打开浏览器:http://localhost:3000/
看到如下效果 react ssr已经实现了:
2.4 为组件元素附加事件的方式
添加点击按钮事件没有响应,分析问题,我们发现只有html代码,没有js代码
实现思路:
- 在客户端组件进行二次“渲染”,为元素附加事件。
- 客户端二次渲染 hydrate
- 使用 hydrate 方法对组件进行渲染,为组件元素附加事件。
- hydrate 方法在实现渲染的时候,会复用原本已经存在的dom节点,减少重新生成的节点以及删除原本dom节点的开销,通过react-dom带入hydrate。render方法不会重新复用节点。
ReactDOM.hydrate(<App/>, document.getElementById("root"))
- 客户端React打包的配置:
- webpack配置
打包的目的: 转换jsx语法,转换浏览器不识别的高级javascript语法,
打包目