React 下的同构渲染

826 阅读1分钟

昨天发布了如何手动实现一个自己的服务端渲染 Reac SSR。React 的客户端渲染 CSR ,大家都比较熟悉,我就言简意赅地介绍下。

# Client 下的 webpack 配置

在当前项目的目录下创建一个 webpack.client.js 的文件,它是客户端打包的处理文件,内容如下:

//webpack.client.js
const path = require("path")
module.exports = {
    mode:"none",
    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']
                    }
                }
            }
        ]
    }
}

webpack.client.js 这个配置文件的内容跟 webpack.server.js 文件相比,在 entry 的属性值上有所不同。

webpack.client.js 的入口文件是 client 下的 index.js 文件,这个 index.js 文件也是咱们常写的,但是需要注意的是它里面的书写内容有所改动。

//client/index.js
import React from "react"
import ReactDOM from "react-dom"
import Home from "../common/pages/home"
ReactDOM.hydrate(<Home/>,document.getElementById("root"))

这里我们没有使用 ReactDOM.render() 来渲染,而是换成了 ReactDOM.hydrate()。

至于 hydrate 的学习大家可以参考这里

它是对服务端渲染后的结果的二次渲染,因为服务端渲染后的结果是一个纯静态页面。

如果是和用户的一些操作行为相关的 JavaScript 代码就需要 hydrate 的二次渲染,它会把 JavaScript 代码进行加载执行。

还有一个不同的地方就是打包后存放的位置不同。

总结一下两者的处理结果:

  • 服务端渲染,我们将对应在 server 下的 index.js 文件打包到 dist 目录,生成文件叫 build.js

  • 客户端渲染,我们将对应在 client 下的 index.js 文件打包到 public 目录,生成文件叫 bundle.js

# 配置指令

配置文件写好之后,我们就需要配置客户端打包的指令。打开 package.json 文件,在 scripts 中配置客户端打包指令:

//package.json"
scripts": {​

    "dev:server-build": "npx webpack --config webpack.server.js --watch",
    "dev:client-build": "npx webpack --config webpack.client.js --watch"
    "dev:server-run": "nodemon --watch dist --exec \"node dist/build.js\""
  },

这里我们可以看到,我们加了 --watch 参数(功能可以参考 wenpack 配置)。配置打包后,我们也配置了 nodemon 对 dist 目录的监控和对 dist/build.js 的执行。

# 转折重点

现在我们把客户端和服务端的打包都配置成功,紧接着我们需要思考:

服务端打包后生成了静态页面,渲染了出来,那么客户端在二次渲染的时候怎么执行 public/bundle.js 文件?

需要我们分两步解决:

1、我们需要在服务端 server 下的文件 index.js 添加 script 标签,引入客户端打包后的 bundle.js

//server/index.js
import app from "./http"
import React from "react"
import Home from "../common/pages/home";
import { renderToString } from "react-dom/server"
app.get("/",(req,res)=>{
    const content = renderToString(<Home/>)
    res.send(`
        <html>
            <head>
            </head>
            <body>
                <div id="root">${content}</div>
            </body>
            <script src="build.js"></script>
        </html>
    `)
})

2、配置静态文件

在 server/http.js 中配置静态资源文件

//server/http.js
// import express from "express"
const express = require("express")
const app = express()
app.use(express.static("public"))
app.listen(3000,() => {
    console.log("sdfdsfsf")
})
export default app;

到此,一个完整的服务端渲染和客户端渲染同时存在的小项目就搭建成功了。对于这种两者兼具的渲染方式,我们把它称为同构渲染

接下来,我们来聊聊同构渲染常见的优化手段。