深入探究CSR与SSR并实现Mini React SSR
1. 使用了 `import` 语法,这是 `ES规范`,而非 Node.js 的 `CommonJS规范`。
2. 我们使用了 `React` 的 `JSX` 语法,`JavaScript` 并不认识,需要用 `Babel` 转译成这样。
修改 `package.json`:
} ```
此时运行 node server.js
可以成功渲染,但是只用 renderToString
将 React组件
转为了 HTML字符串
,并没有任何的 Hydrate
(水合)事件绑定,只有静态的 HTML。
绑定事件
既然服务端只能渲染 HTML,客户端能渲染事件,那就结合一下,再实现一遍 CSR
,让页面插入一个打包后的 bundle
,挂载到相同的节点,让客户端将一模一样的内容重新渲染一遍,并绑定上事件(重复渲染后面解决)。
client.js
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './pages/index';
const root = createRoot(document.getElementById('root'));
root.render(<App />);
修改 server.js
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 />);
app.get('/', (req, res) => res.send(`
<html>
<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
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
"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
import React from 'react';
import { hydrateRoot } from 'react-dom/client';
import App from './pages/index';
const root = hydrateRoot(document.getElementById('root'));
root.render(<App />);
运行 npm start
,实现了 SSR + Hydration 的 React 渲染。