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

如何避免Zustand更新相同值副本时触发组件重渲染?

Zustand避免克隆副本触发不必要重渲染的解决方案

问题

在使用Zustand进行状态管理时,将状态变量更新为其值的克隆副本会触发组件不必要的重渲染。相关代码与现象如下:

Zustand Store 代码

export const useStore = create<State>((set) => ({
 users: [],
 setUsers: (users: User[]) => set(state => ({...state, users }))
}));

触发更新的组件代码

export default function App() {
  const users = useStore((state) => state.users);
  const setUsers = useStore((state) => state.setUsers);

  return (
    <div className="App">
      <button
        onClick={() =>
          setUsers([
            ...users,
            { name: "some name", age: Math.floor(Math.random() * 100) }
          ])
        }
      >
        Add User
      </button>
      <button onClick={() => setUsers(users)}>Refresh exact</button>
      <button onClick={() => setUsers([...users])}>Refresh clone</button>
      <Users />
    </div>
  );
}

订阅状态的Users组件代码

export const Users = () => {
  const users = useStore((state) => state.users);

  console.log("rerendered");

  return (
    <>
      {users.map((user, i) => (
        <div key={i}>{JSON.stringify(user, null, 2)}</div>
      ))}
    </>
  );
};

现象说明

  • 添加用户时,Users组件正常重渲染,符合预期;
  • 调用setUsers(users)(赋值自身引用)时,组件无重渲染,符合预期;
  • 调用setUsers([...users])(赋值克隆副本)时,组件会触发重渲染——原因是克隆后的数组与原数组引用不相等(users !== [...users]),Zustand默认通过引用相等性判断是否更新状态,进而触发组件重渲染。

解决方案

1. 在Store层添加值相等性校验,拦截无意义的状态更新

修改setUsers方法,在更新前对比新旧值的内容是否一致,仅当内容不同时才执行状态更新。可根据数据复杂度选择合适的比较方式:

方案1.1:使用JSON.stringify(适合简单/中等复杂度数据)

export const useStore = create<State>((set) => ({
 users: [],
 setUsers: (newUsers: User[]) => set(state => {
   // 对比数组内容是否一致
   if (JSON.stringify(state.users) === JSON.stringify(newUsers)) {
     return state; // 内容一致,不更新状态
   }
   return {...state, users: newUsers };
 })
}));

方案1.2:使用深比较工具(如lodash.isEqual,适合复杂数据)

先安装lodash,再修改Store:

import isEqual from 'lodash.isEqual';

export const useStore = create<State>((set) => ({
 users: [],
 setUsers: (newUsers: User[]) => set(state => {
   if (isEqual(state.users, newUsers)) {
     return state; // 内容一致,不更新状态
   }
   return {...state, users: newUsers };
 })
}));

2. 在组件层使用记忆化选择器,基于值相等性订阅状态

Zustand支持通过自定义比较函数优化选择器,让组件仅在状态内容真正变化时重渲染:

方案2.1:使用Zustand内置的shallow比较

import { shallow } from 'zustand/shallow';

export const Users = () => {
  // 使用shallow比较数组元素是否一致
  const users = useStore((state) => state.users, shallow);

  console.log("rerendered");

  return (
    <>
      {users.map((user, i) => (
        <div key={i}>{JSON.stringify(user, null, 2)}</div>
      ))}
    </>
  );
};

方案2.2:自定义深比较函数

如果需要更精细的控制,可自定义深比较逻辑:

import isEqual from 'lodash.isEqual';

export const Users = () => {
  // 仅当数组内容真正变化时,组件才重渲染
  const users = useStore((state) => state.users, (prev, next) => isEqual(prev, next));

  console.log("rerendered");

  return (
    <>
      {users.map((user, i) => (
        <div key={i}>{JSON.stringify(user, null, 2)}</div>
      ))}
    </>
  );
};

3. 自定义中间件统一处理状态更新校验(可选)

如果多个状态都需要类似的校验,可以自定义Zustand中间件,统一拦截无意义的状态更新,避免在每个setter中重复编写比较逻辑。


内容的提问来源于stack exchange,提问作者Seth Lutske

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 17:17:52