关于前端:面试官说说React服务端渲染怎么做原理是什么

43次阅读

共计 4813 个字符,预计需要花费 13 分钟才能阅读完成。

关注公众号

关注公众号获取代码以及最新教程和文章,也能够分割作者,获取帮忙

一、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 代码,并通过 expressstatic中间件为 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 门路,而后在已有的路由表内查找到对应的组件,拿到须要申请的数据,将数据作为 propscontext 或者store 模式传入组件

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

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

参考文献

  • https://zhuanlan.zhihu.com/p/52693113
  • https://segmentfault.com/a/1190000020417285
  • https://juejin.cn/post/6844904000387563533#heading-14

正文完
 0