Next.js搭配React 18时状态与视图更新不同步问题咨询
Next.js 搭配 React 开发时状态与视图不同步问题
问题现象
使用如下版本依赖开发时,出现状态更新后视图未同步渲染的异常:点击排序按钮切换排序方向时,排序状态的文本展示正常切换,但绑定的数组排序结果不更新,需要多次点击按钮才能触发视图变更。
依赖版本:
"next": "12.1.6", "react": "18.2.0", "react-dom": "18.2.0",
两种复现写法如下:
useMemo 实现版本(异常)
import { useState, useMemo } from "react"; const items = [ { id: 1, name: "apple" }, { id: 2, name: "orange" }, { id: 3, name: "mango" } ]; export default function IndexPage() { const [desc, setDesc] = useState(true); const sortedItems = useMemo(() => [...(desc ? items : items.reverse())], [ desc ]); return ( <div> <button onClick={() => setDesc((s) => !s)}>change order</button> <br /> <span>desc: {desc.toString()}</span> <pre>{JSON.stringify(sortedItems, null, 2)}</pre> </div> ); }
useState 实现版本(异常)
import { useState } from "react"; const items = [ { id: 1, name: "apple" }, { id: 2, name: "orange" }, { id: 3, name: "mango" } ]; export default function IndexPage() { const [state, setState] = useState({ desc: true, items }); function handleSort() { setState((s) => s.desc ? { desc: false, items: [...items.reverse()] } : { desc: true, items: [...items] } ); } return ( <div> <button onClick={handleSort}>change order</button> <br /> <span>desc: {state.desc.toString()}</span> <pre>{JSON.stringify(state.items, null, 2)}</pre> </div> ); }
问题原因
该异常不是框架Bug,属于代码写法错误,核心问题是直接调用了会原地修改原数组的reverse()方法,污染了顶层定义的原始数据源:
- JavaScript中
Array.reverse()属于原地修改方法,不会生成新数组,会直接修改调用该方法的原数组本身。 - 两个示例中,第一次点击按钮触发倒序时,
items.reverse()直接把顶层常量items的内容改成了倒序,后续再点击按钮切换回正序时,读取到的items已经是被修改过的倒序内容,浅拷贝后的值和当前状态里的数组值没有差异,React通过浅比较识别不到状态变化,就会跳过渲染,导致视图不同步。
修复方法
所有涉及数组修改的逻辑,遵循「不修改原始数据源」的原则:调用原地修改数组的方法前,先创建原数组的浅副本,再对副本做修改。
修复后的 useMemo 版本
import { useState, useMemo } from "react"; const items = [ { id: 1, name: "apple" }, { id: 2, name: "orange" }, { id: 3, name: "mango" } ]; export default function IndexPage() { const [desc, setDesc] = useState(true); // 先拷贝数组再调用reverse,不修改原始items const sortedItems = useMemo(() => desc ? items : [...items].reverse(), [desc]); return ( <div> <button onClick={() => setDesc((s) => !s)}>change order</button> <br /> <span>desc: {desc.toString()}</span> <pre>{JSON.stringify(sortedItems, null, 2)}</pre> </div> ); }
修复后的 useState 版本
import { useState } from "react"; const items = [ { id: 1, name: "apple" }, { id: 2, name: "orange" }, { id: 3, name: "mango" } ]; export default function IndexPage() { const [state, setState] = useState({ desc: true, items }); function handleSort() { setState((s) => s.desc // 先拷贝数组再反转,避免修改原始items ? { desc: false, items: [...items].reverse() } : { desc: true, items: [...items] } ); } return ( <div> <button onClick={handleSort}>change order</button> <br /> <span>desc: {state.desc.toString()}</span> <pre>{JSON.stringify(state.items, null, 2)}</pre> </div> ); }
开发注意事项
- 除了
reverse()之外,sort()、splice()、push()、pop()、shift()、unshift()都属于原地修改数组的方法,在React状态计算、更新逻辑中调用前,必须先对原数组做浅拷贝,避免污染源数据。 - React对状态更新做浅比较,如果直接修改状态引用本身而不是生成新的引用,React会判定状态未发生变化,跳过组件重渲染,引发视图不同步问题。
内容的提问来源于stack exchange,提问作者boop_the_snoot
相关产品推荐
相关产品推荐

