关注公众号
关注公众号获取代码以及最新教程和文章,也能够分割作者,获取帮忙
一、SSR
在SSR中,咱们理解到Server-Side Rendering
,简称SSR
,意为服务端渲染
指由服务侧实现页面的 HTML
构造拼接的页面解决技术,发送到浏览器,而后为其绑定状态与事件,成为齐全可交互页面的过程
其解决的问题次要有两个:
- SEO,因为搜索引擎爬虫抓取工具能够间接查看齐全渲染的页面
- 减速首屏加载,解决首屏白屏问题
二、如何做
在react
中,实现SSR
次要有两种模式:
- 手动搭建一个 SSR 框架
- 应用成熟的SSR 框架,如 Next.JS
这里次要以手动搭建一个SSR
框架进行实现
首先通过express
启动一个app.js
文件,用于监听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('Exampleapp listening on port 3000!'))
而后再服务器中编写react
代码,在app.js
中进行应援用
import React from 'react'
const Home = () =>{
return <div>home</div>
}
export default Home
为了让服务器可能辨认JSX
,这里须要应用webpakc
对我的项目进行打包转换,创立一个配置文件webpack.server.js
并进行相干配置,如下:
const path = require('path') //node的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') //寄存到根目录的build文件夹
},
externals: [nodeExternals()], //放弃node中require的援用形式
module: {
rules: [{ //打包规定
test: /\.js?$/, //对所有js文件进行打包
loader:'babel-loader', //应用babel-loader进行打包
exclude: /node_modules/,//不打包node_modules中的js文件
options: {
presets: ['react','stage-0',['env', {
//loader时额定的打包规定,对react,JSX,ES6进行转换
targets: {
browsers: ['last 2versions'] //对支流浏览器最近两个版本进行兼容
}
}]]
}
}]
}
}
接着借助react-dom
提供了服务端渲染的 renderToString
办法,负责把React
组件解析成html
import express from 'express'
import React from 'react'//引入React以反对JSX的语法
import { renderToString } from 'react-dom/server'//引入renderToString办法
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('Exampleapp listening on port 3001!'))
下面的过程中,曾经可能胜利将组件渲染到了页面上
然而像一些事件处理的办法,是无奈在服务端实现,因而须要将组件代码在浏览器中再执行一遍,这种服务器端和客户端共用一套代码的形式就称之为同构
重构艰深讲就是一套React代码在服务器上运行一遍,达到浏览器又运行一遍:
- 服务端渲染实现页面构造
- 浏览器端渲染实现事件绑定
浏览器实现事件绑定的形式为让浏览器去拉取JS
文件执行,让JS
代码来管制,因而须要引入script
标签
通过script
标签为页面引入客户端执行的react
代码,并通过express
的static
中间件为js
文件配置路由,批改如下:
import express from 'express'
import React from 'react'//引入React以反对JSX的语法
import { renderToString } from'react-dom/server'//引入renderToString办法
import Home from './src/containers/Home'
const app = express()
app.use(express.static('public'));
//应用express提供的static中间件,中间件会将所有动态文件的路由指向public文件夹
const content = renderToString(<Home/>)
app.get('/',(req,res)=>res.send(`
<html>
<head>
<title>ssr demo</title>
</head>
<body>
${content}
<script src="/index.js"></script>
</body>
</html>
`))
app.listen(3001, () =>console.log('Example app listening on port 3001!'))
而后再客户端执行以下react
代码,新建webpack.client.js
作为客户端React代码的webpack
配置文件如下:
const path = require('path') //node的path模块
module.exports = {
mode:'development', //开发模式
entry:'./src/client/index.js', //入口
output: { //打包进口
filename:'index.js', //打包后的文件名
path:path.resolve(__dirname,'public') //寄存到根目录的build文件夹
},
module: {
rules: [{ //打包规定
test: /\.js?$/, //对所有js文件进行打包
loader:'babel-loader', //应用babel-loader进行打包
exclude: /node_modules/, //不打包node_modules中的js文件
options: {
presets: ['react','stage-0',['env', {
//loader时额定的打包规定,这里对react,JSX进行转换
targets: {
browsers: ['last 2versions'] //对支流浏览器最近两个版本进行兼容
}
}]]
}
}]
}
}
这种办法就可能简略实现首页的react
服务端渲染,过程对应如下图:
在做完初始渲染的时候,一个利用会存在路由的状况,配置信息如下:
import React from 'react' //引入React以反对JSX
import { Route } from 'react-router-dom' //引入路由
import Home from './containers/Home' //引入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'//引入React以反对JSX的语法
import { renderToString } from 'react-dom/server'//引入renderToString办法
import { StaticRouter } from 'react-router-dom'
import Router from '../Routers'
const app = express()
app.use(express.static('public'));
//应用express提供的static中间件,中间件会将所有动态文件的路由指向public文件夹
app.get('/',(req,res)=>{
const content = renderToString((
//传入以后path
//context为必填参数,用于服务端渲染参数传递
<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('Exampleapp listening on port 3001!'))
这样也就实现了路由的服务端渲染
三、原理
整体react
服务端渲染原理并不简单,具体如下:
node server
接管客户端申请,失去以后的申请url
门路,而后在已有的路由表内查找到对应的组件,拿到须要申请的数据,将数据作为 props
、context
或者store
模式传入组件
而后基于 react
内置的服务端渲染办法 renderToString()
把组件渲染为 html
字符串在把最终的 html
进行输入前须要将数据注入到浏览器端
浏览器开始进行渲染和节点比照,而后执行实现组件内事件绑定和一些交互,浏览器重用了服务端输入的 html
节点,整个流程完结
参考文献
- https://zhuanlan.zhihu.com/p/52693113
- https://segmentfault.com/a/1190000020417285
- https://juejin.cn/post/6844904000387563533#heading-14
发表回复