You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 01:27:16