如何阻止useState/useEffect在初始加载时修改预渲染HTML
问题原因与解决方案
你遇到的核心问题不是useEffect的执行逻辑,而是组件初始state与预渲染DOM不匹配:
你的todos初始值设为null,当React通过hydrateRoot挂载组件时,会对比组件的render输出与现有预渲染DOM。此时组件因为todos是null,不会渲染任何todo项,React会直接清除页面中预渲染好的todo节点——这和useEffect是否执行完全无关。
解决步骤
让初始state与预渲染数据对齐
预渲染阶段已经生成了todos数据,你需要把这些数据作为todos的初始state。可以在SSG时将数据注入全局变量(比如window.__PRE_RENDERED_TODOS__),或者直接在组件中传入预渲染数据。修正
useEffect的依赖项
你之前写的useEffect依赖是true,这是无效语法。React的依赖数组需要传入state、props或其他会变化的变量,这里应该依赖控制是否刷新的keep_pre_rendered_todos。优化渲染逻辑
用可选链?.替代todos &&的判断,语法更简洁,同时确保初始渲染时能匹配预渲染DOM。
修改后的代码示例
假设SSG阶段已将预渲染的todos存入全局变量window.__PRE_RENDERED_TODOS__:
// 从预渲染全局变量获取初始数据 const initialTodos = window.__PRE_RENDERED_TODOS__ || null; const [keepPreRenderedTodos, updatePreRenderedTodos] = useState(true); const [todos, setData] = useState(initialTodos); useEffect(() => { if(keepPreRenderedTodos) return; fetch("https://jsonplaceholder.typicode.com/todos") .then((response) => response.json()) .then((fetchedTodos) => setData(fetchedTodos)) .catch((error) => { alert(`${error} 发生错误!`) }); }, [keepPreRenderedTodos]); // 修正为合法的依赖项 return ( <div className="todos"> { todos?.map((task) => <div className="todo" key={task.id}> {task.title} </div> ) } </div> );
关键说明
- 初始state使用预渲染数据,确保组件第一次render的输出与现有DOM完全匹配,React不会清除预渲染内容。
- 只有当你主动将
keepPreRenderedTodos设为false时,才会触发fetch请求更新todos。
内容的提问来源于stack exchange,提问作者jon
相关产品推荐
相关产品推荐

