在 React 服务端渲染场景下,如果我们在组件渲染阶段直接调用 Math.random 来生成随机数并展示,那么服务端输出的 HTML 和客户端首次渲染的 HTML 往往不一致,这会触发 React 的 hydration 警告甚至界面闪烁。使用 useEffect 可以把随机数的生成推迟到客户端挂载之后,从而解决这一冲突。

为什么随机数会导致服务端渲染冲突
服务端渲染时,React 在服务器上运行组件函数生成 HTML 字符串;客户端拿到这段 HTML 后,会用 JavaScript 再次执行组件来做 hydration 校验。如果组件函数里写了如下代码:
function RandomView() {
const num = Math.random();
return <div>{num}</div>;
}
服务器生成的 num 和浏览器生成的 num 几乎必然不同,于是两端 DOM 文本不匹配。React 会抛出类似 Text content does not match server-rendered HTML 的警告,并可能用客户端内容覆盖服务端内容,造成闪烁。
使用 useEffect 的基本思路
核心原则是:首屏渲染不使用随机数,而是给一个固定的占位值;等组件在客户端挂载完成,再在 useEffect 里生成随机数并更新状态。这样服务端和客户端首次渲染输出的都是占位值,hydration 就能通过。
基础实现示例
import React, { useState, useEffect } from 'react';
function RandomView() {
// 先用固定值,保证服务端和客户端首次渲染一致
const [num, setNum] = useState(0);
useEffect(() => {
// 仅在客户端挂载后生成随机数
setNum(Math.random());
}, []);
return <div>当前随机数:{num}</div>;
}
export default RandomView;
上述代码中,初始状态为 0,服务端输出 0,客户端首次渲染也是 0,hydrate 无冲突。随后 useEffect 执行,更新为浏览器本地随机数。
处理多个随机值的场景
如果需要一组随机数,比如渲染一个随机色块列表,同样可以先给空数组或占位数组,再在 useEffect 中填充:
import React, { useState, useEffect } from 'react';
function ColorList() {
const [colors, setColors] = useState([]);
useEffect(() => {
const list = Array.from({ length: 3 }, () =>
'#' + Math.floor(Math.random() * 0xffffff).toString(16).padStart(6, '0')
);
setColors(list);
}, []);
return (
<ul>
{colors.length === 0
? <li>加载中...</li>
: colors.map((c, i) => (
<li key={i} style={{ color: c }}>颜色 {c}</li>
))}
</ul>
);
}
export default ColorList;
注意事项
- useEffect 不会在服务端执行,因此依赖它的数据只存在于客户端,要避免在渲染逻辑里假设它已就绪。
- 如果随机数影响 SEO 关键内容,应考虑由服务端传入固定种子,而不是完全依赖客户端随机。
- 使用随机图片地址或外部资源时,也要注意服务端和客户端是否一致,必要时用 useEffect 赋值。
小结
通过把随机数生成逻辑移入 useEffect,我们让组件在服务端和客户端首次渲染时输出一致的结构,随后在客户端安全地更新随机内容。这是解决 React 服务端渲染中随机数冲突最直接、成本最低的做法。