深入探究CSR与SSR并实现Mini React SSR
1. 使用了 `import` 语法,这是 `ES规范`,而非 Node.js 的 `CommonJS规范`。
2. 我们使用了 `React` 的 `JSX` 语法,`JavaScript` 并不认识,需要用 `Babel` 转译成这样。
修改 `package.json`:
json "scripts": { "start": "webpack --config webpack.server.js && node ./build/server.bundle.js" }
此时运行 `node server.js` 可以成功渲染,但是只用 `renderToString` 将 `React组件` 转为了 `HTML字符串`,并没有任何的 `Hydrate`(水合)事件绑定,只有静态的 HTML。
---
## 绑定事件
既然服务端只能渲染 HTML,客户端能渲染事件,那就结合一下,再实现一遍 `CSR`,让页面插入一个打包后的 `bundle`,挂载到相同的节点,让客户端将一模一样的内容重新渲染一遍,并绑定上事件(重复渲染后面解决)。
### `client.js`
javascript import React from 'react'; import { createRoot } from 'react-dom/client'; import App from './pages/index';
const root = createRoot(document.getElementById('root'));
root.render(
### 修改 `server.js`
javascript import express from 'express'; import React from 'react'; import { renderToString } from 'react-dom/server'; import App from './pages/index';
const app = express();
app.use(express.static('public'));
const content = renderToString(
app.get('/', (req, res) => res.send(`
<head>
<title>Tiny React SSR</title>
</head>
<body>
<div id='root'>
${content}
</div>
<script src='/client.bundle.js'></script>
</body></html>
`));
app.listen(3000, () => console.log('listening on port 3000'));
打包后的客户端代码被命名为 `client.bundle.js`,并放入 `public` 目录。
### `webpack.client.js`
javascript const path = require('path');
module.exports = { mode: 'development', entry: './client.js', output: {
filename: 'client.bundle.js',
path: path.resolve(__dirname, 'public'),}, module: {
rules: [
{
test: /.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react'],
},
},
},
],}, };
### 现在的流程是
先打包 `客户端` 的 `JS`,将引用 `pages/index.js` 核心 React 代码的 `client.js` 打包到 `public` 下的 `client.bundle.js`。
然后将同样引用 `pages/index.js` 核心 React 代码的 `server.js` 打包到 `build` 下的 `server.bundle.js` 中,然后 `node` 开启 `server.bundle.js`。
服务端会先渲染一遍组件代码,然后输出到 HTML 中,引用 `client.bundle.js`,然后用 JS 再渲染一遍,并绑定上事件。
### 修改 `package.json`
json "scripts": { "start": "webpack --config webpack.client.js && webpack --config webpack.server.js && node ./build/server.bundle.js" }
此时已经能交互,但是有个重复渲染问题需要解决。
### `HydrateRoot`
React 官方提供了 `HydrateRoot` API,允许你在先前 `react-dom/server` 生成的 HTML `DOM节点` 中展示 `React组件`,简单来说就是**复用服务器渲染出来的 DOM 节点**。
### 修改 `client.js`
javascript import React from 'react'; import { hydrateRoot } from 'react-dom/client'; import App from './pages/index';
const root = hydrateRoot(document.getElementById('root'));
root.render(
运行 `npm start`,实现了 SSR + Hydration 的 React 渲染。
<style>
p {
text-indent: 2em; /* 首行缩进两个字 */
margin-bottom: 1em; /* 段落间距 */
line-height: 1.6; /* 行高 */
}
</style>