基本原理
- 同构渲染:SSR场景下,React应用在服务器端和客户端都要进行渲染。在服务器端渲染出HTML,包含初始状态,传递给客户端。客户端拿到HTML后,基于已有的HTML和状态进行“注水”(hydration),即重新挂载React应用,使得客户端状态和服务器端渲染时的状态一致。
- 状态序列化:为保证一致性,需将服务器端的状态序列化为字符串,嵌入到HTML中。客户端获取该序列化状态,反序列化后恢复状态。
可能采取的措施
- 使用框架内置机制:
- 如Next.js,通过
getInitialProps
(在页面组件中)或getServerSideProps
(新的推荐方式)等方法,在服务器端获取数据并初始化状态。Next.js会自动处理状态在客户端和服务器端的同步,将服务器端获取的数据传递给客户端。
- Nuxt.js也有类似机制,如
asyncData
方法,用于在服务器端获取数据并填充到组件状态,确保两端状态一致。
- 手动处理状态同步:
- 状态存储与传递:在服务器端,使用
useState
或useReducer
初始化状态后,将状态存储在一个对象中。例如:
import React, { useState } from'react';
const MyComponent = () => {
const [count, setCount] = useState(0);
const serverState = { count };
return <div>{count}</div>;
};
- **序列化与嵌入**:将`serverState`序列化为JSON字符串,嵌入到HTML的某个标签属性或`<script>`标签中。比如在Node.js服务器端使用Express框架:
const express = require('express');
const React = require('react');
const ReactDOMServer = require('react-dom/server');
const app = express();
app.get('*', (req, res) => {
const html = ReactDOMServer.renderToString(<MyComponent />);
const serverState = { /* 获取MyComponent中的状态 */ };
const serializedState = JSON.stringify(serverState);
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>SSR with React Hooks</title>
</head>
<body>
<div id="root">${html}</div>
<script>
window.__INITIAL_STATE__ = ${serializedState};
</script>
<script src="/client.js"></script>
</body>
</html>
`);
});
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
- **客户端恢复状态**:在客户端,从`window.__INITIAL_STATE__`获取序列化状态,反序列化后设置到`useState`或`useReducer`中。例如:
import React, { useState, useEffect } from'react';
const MyComponent = () => {
const initialState = window.__INITIAL_STATE__;
const [count, setCount] = useState(initialState? initialState.count : 0);
useEffect(() => {
// 后续状态更新逻辑
}, []);
return <div>{count}</div>;
};
- 使用第三方库:如
react - dehydrate
和react - rehydrate
,它们帮助在服务器端“脱水”(dehydrate)状态,在客户端“注水”(rehydrate)状态,确保两端状态一致。