• React之服务端渲染


    一、是什么

    SSR中 (opens new window),我们了解到Server-Side Rendering ,简称SSR,意为服务端渲染

    指由服务侧完成页面的 HTML 结构拼接的页面处理技术,发送到浏览器,然后为其绑定状态与事件,成为完全可交互页面的过程

    其解决的问题主要有两个:

    • SEO,由于搜索引擎爬虫抓取工具可以直接查看完全渲染的页面
    • 加速首屏加载,解决首屏白屏问题

    二、如何做

    react中,实现SSR主要有两种形式:

    • 手动搭建一个 SSR 框架
    • 使用成熟的SSR 框架,如 Next.JS

    这里主要以手动搭建一个SSR框架进行实现

    首先通过express启动一个app.js文件,用于监听3000端口的请求,当请求根目录时,返回HTML,如下:

    1. const express = require('express')
    2. const app = express()
    3. app.get('/', (req,res) => res.send(`
    4. <html>
    5. <head>
    6. <title>ssr demo</title>
    7. </head>
    8. <body>
    9. Hello world
    10. </body>
    11. </html>
    12. `))
    13. app.listen(3000, () => console.log('Exampleapp listening on port 3000!'))

    然后再服务器中编写react代码,在app.js中进行应引用

    1. import React from 'react'
    2. const Home = () =>{
    3. return <div>home</div>
    4. }
    5. export default Home

    为了让服务器能够识别JSX,这里需要使用webpakc对项目进行打包转换,创建一个配置文件webpack.server.js并进行相关配置,如下:

    1. const path = require('path') //node的path模块
    2. const nodeExternals = require('webpack-node-externals')
    3. module.exports = {
    4. target:'node',
    5. mode:'development', //开发模式
    6. entry:'./app.js', //入口
    7. output: { //打包出口
    8. filename:'bundle.js', //打包后的文件名
    9. path:path.resolve(__dirname,'build') //存放到根目录的build文件夹
    10. },
    11. externals: [nodeExternals()], //保持node中require的引用方式
    12. module: {
    13. rules: [{ //打包规则
    14. test: /\.js?$/, //对所有js文件进行打包
    15. loader:'babel-loader', //使用babel-loader进行打包
    16. exclude: /node_modules/,//不打包node_modules中的js文件
    17. options: {
    18. presets: ['react','stage-0',['env', {
    19. //loader时额外的打包规则,对react,JSX,ES6进行转换
    20. targets: {
    21. browsers: ['last 2versions'] //对主流浏览器最近两个版本进行兼容
    22. }
    23. }]]
    24. }
    25. }]
    26. }
    27. }

    接着借助react-dom提供了服务端渲染的 renderToString方法,负责把React组件解析成html

    1. import express from 'express'
    2. import React from 'react'//引入React以支持JSX的语法
    3. import { renderToString } from 'react-dom/server'//引入renderToString方法
    4. import Home from'./src/containers/Home'
    5. const app= express()
    6. const content = renderToString(<Home/>)
    7. app.get('/',(req,res) => res.send(`
    8. <html>
    9. <head>
    10. <title>ssr demo</title>
    11. </head>
    12. <body>
    13. ${content}
    14. </body>
    15. </html>
    16. `))
    17. app.listen(3001, () => console.log('Exampleapp listening on port 3001!'))

    上面的过程中,已经能够成功将组件渲染到了页面上

    但是像一些事件处理的方法,是无法在服务端完成,因此需要将组件代码在浏览器中再执行一遍,这种服务器端和客户端共用一套代码的方式就称之为同构

    重构通俗讲就是一套React代码在服务器上运行一遍,到达浏览器又运行一遍:

    • 服务端渲染完成页面结构
    • 浏览器端渲染完成事件绑定

    浏览器实现事件绑定的方式为让浏览器去拉取JS文件执行,让JS代码来控制,因此需要引入script标签

    通过script标签为页面引入客户端执行的react代码,并通过expressstatic中间件为js文件配置路由,修改如下:

    1. import express from 'express'
    2. import React from 'react'//引入React以支持JSX的语法
    3. import { renderToString } from'react-dom/server'//引入renderToString方法
    4. import Home from './src/containers/Home'
    5. const app = express()
    6. app.use(express.static('public'));
    7. //使用express提供的static中间件,中间件会将所有静态文件的路由指向public文件夹
    8. const content = renderToString(<Home/>)
    9. app.get('/',(req,res)=>res.send(`
    10. <html>
    11. <head>
    12. <title>ssr demo</title>
    13. </head>
    14. <body>
    15. ${content}
    16. <script src="/index.js"></script>
    17. </body>
    18. </html>
    19. `))
    20. app.listen(3001, () =>console.log('Example app listening on port 3001!'))

    然后再客户端执行以下react代码,新建webpack.client.js作为客户端React代码的webpack配置文件如下:

    1. const path = require('path') //node的path模块
    2. module.exports = {
    3. mode:'development', //开发模式
    4. entry:'./src/client/index.js', //入口
    5. output: { //打包出口
    6. filename:'index.js', //打包后的文件名
    7. path:path.resolve(__dirname,'public') //存放到根目录的build文件夹
    8. },
    9. module: {
    10. rules: [{ //打包规则
    11. test: /\.js?$/, //对所有js文件进行打包
    12. loader:'babel-loader', //使用babel-loader进行打包
    13. exclude: /node_modules/, //不打包node_modules中的js文件
    14. options: {
    15. presets: ['react','stage-0',['env', {
    16. //loader时额外的打包规则,这里对react,JSX进行转换
    17. targets: {
    18. browsers: ['last 2versions'] //对主流浏览器最近两个版本进行兼容
    19. }
    20. }]]
    21. }
    22. }]
    23. }
    24. }

    这种方法就能够简单实现首页的react服务端渲染,过程对应如下图:

    在做完初始渲染的时候,一个应用会存在路由的情况,配置信息如下:

    1. import React from 'react' //引入React以支持JSX
    2. import { Route } from 'react-router-dom' //引入路由
    3. import Home from './containers/Home' //引入Home组件
    4. export default (
    5. <div>
    6. <Route path="/" exact component={Home}></Route>
    7. </div>
    8. )

    然后可以通过index.js引用路由信息,如下:

    1. import React from 'react'
    2. import ReactDom from 'react-dom'
    3. import { BrowserRouter } from'react-router-dom'
    4. import Router from'../Routers'
    5. const App= () => {
    6. return (
    7. <BrowserRouter>
    8. {Router}
    9. </BrowserRouter>
    10. )
    11. }
    12. ReactDom.hydrate(<App/>, document.getElementById('root'))

    这时候控制台会存在报错信息,原因在于每个Route组件外面包裹着一层div,但服务端返回的代码中并没有这个div

    解决方法只需要将路由信息在服务端执行一遍,使用使用StaticRouter来替代BrowserRouter,通过context进行参数传递

    1. import express from 'express'
    2. import React from 'react'//引入React以支持JSX的语法
    3. import { renderToString } from 'react-dom/server'//引入renderToString方法
    4. import { StaticRouter } from 'react-router-dom'
    5. import Router from '../Routers'
    6. const app = express()
    7. app.use(express.static('public'));
    8. //使用express提供的static中间件,中间件会将所有静态文件的路由指向public文件夹
    9. app.get('/',(req,res)=>{
    10. const content = renderToString((
    11. //传入当前path
    12. //context为必填参数,用于服务端渲染参数传递
    13. <StaticRouter location={req.path} context={{}}>
    14. {Router}
    15. </StaticRouter>
    16. ))
    17. res.send(`
    18. <html>
    19. <head>
    20. <title>ssr demo</title>
    21. </head>
    22. <body>
    23. <div id="root">${content}</div>
    24. <script src="/index.js"></script>
    25. </body>
    26. </html>
    27. `)
    28. })
    29. app.listen(3001, () => console.log('Exampleapp listening on port 3001!'))

    这样也就完成了路由的服务端渲染

    三、原理

    整体react服务端渲染原理并不复杂,具体如下:

    node server 接收客户端请求,得到当前的请求url 路径,然后在已有的路由表内查找到对应的组件,拿到需要请求的数据,将数据作为 propscontext或者store 形式传入组件

    然后基于 react 内置的服务端渲染方法 renderToString()把组件渲染为 html字符串在把最终的 html进行输出前需要将数据注入到浏览器端

    浏览器开始进行渲染和节点对比,然后执行完成组件内事件绑定和一些交互,浏览器重用了服务端输出的 html 节点,整个流程结束

  • 相关阅读:
    TG Pro for Mac强大的硬件温度检测、风扇控制工具测评
    用前端写桌面应用
    商城项目09_品牌管理菜单、快速显示开关、阿里云进行文件上传、结合Alibaba管理OSS、服务端签名后直传
    OpenCV的C#版本EmguCV-1、安装和环境配置
    Redis数据结构解析
    【路径规划-多式联运】基于遗传算法求解多式联运冷链运输成本优化问题附matlab代码
    Kubernetes Kubelet 状态更新机制
    (vue)vue导出excel文件打不开,或者文件内容为object object
    机器学习从入门到放弃:硬train一发手写数字识别
    c3p0连接池配置
  • 原文地址:https://blog.csdn.net/Ming_xm/article/details/134051172