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

React Native运动追踪项目中Apollo Client与Zustand结合的价值疑问

Apollo Client 与 Zustand 搭配的价值及优化建议

核心结论

Apollo Client 和 Zustand 并非重复工具,而是各司其职的状态管理方案:Apollo 专注服务端同步数据的缓存与请求管理,Zustand 负责客户端本地生成的状态。你的当前代码存在不必要的重复存储,但两者结合在特定场景下有明确价值。

一、两者的核心定位差异

  • Apollo Client:
    • 管理从服务端拉取的、需与后端同步的数据,自带全局缓存、请求去重、缓存自动更新(如 mutations 后自动关联查询更新)、分页/加载状态管理等能力。
    • 全局可访问,任意组件通过 useQuery 即可直接获取缓存数据,无需手动同步。
  • Zustand:
    • 管理客户端本地生成的、无需和后端同步的状态,比如:
      • UI 状态(侧边栏展开/收起、选中的标签页)
      • 临时表单数据、本地筛选/排序后的衍生数据
      • 用户本地偏好(主题模式、默认视图)

二、你当前代码的问题:不必要的重复存储

你将 Apollo 获取的 data 存入 Zustand,属于重复存储服务端数据——因为 Apollo 的缓存已经是全局可访问的,直接在其他组件用 useQuery(GET_EXERCICES) 就能拿到最新数据,完全不需要额外存到 Zustand 里,这只会增加维护成本(比如需要手动同步缓存更新)。

三、两者结合的合理场景

只有当你需要对服务端数据做本地加工,或结合客户端状态时,才需要搭配使用:

  1. 衍生本地状态:
    比如从 Apollo 拿到原始运动列表后,需要做本地筛选、排序、分组,这些计算后的结果可以存在 Zustand 里,避免每个组件重复计算。
  2. 客户端临时状态与服务端数据联动:
    比如用户临时收藏某个运动(还没同步到后端),收藏状态存在 Zustand,和 Apollo 返回的运动列表结合展示“已收藏”标记。
  3. 复杂跨组件状态联动:
    多个组件需要共同修改某个和服务端数据相关的本地状态时,Zustand 的全局状态比组件传 props 更高效。

四、优化后的代码示例

场景1:直接使用 Apollo 缓存(无本地加工需求)

去掉 Zustand 的存储逻辑,直接用 Apollo 的数据:

const Exercices = () => {
  const { token } = useAuthStore();
  const { data: exercices, loading, error } = useQuery(GET_EXERCICES, {
    context: {
      headers: {
        Authorization: `Bearer ${token}`,
      },
    },
  });

  if (loading) {
    return <Text>Loading...</Text>;
  }
  if (error) {
    return <Text>Error</Text>;
  }

  // 直接使用 exercices 渲染
  return (
    <View>
      {exercices?.map(exercise => (
        <Text key={exercise.id}>{exercise.name}</Text>
      ))}
    </View>
  );
};

场景2:结合 Zustand 管理本地筛选状态

如果需要本地筛选运动列表,Zustand 负责存储筛选条件和衍生结果:

// Zustand store
const useExerciceStore = create((set, get) => ({
  filterText: '',
  setFilterText: (text) => set({ filterText: text }),
  getFilteredExercices: (rawExercices) => {
    const { filterText } = get();
    return rawExercices?.filter(ex => 
      ex.name.toLowerCase().includes(filterText.toLowerCase())
    ) || [];
  },
}));

// 组件中使用
const Exercices = () => {
  const { token } = useAuthStore();
  const { filterText, setFilterText, getFilteredExercices } = useExerciceStore();
  const { data: rawExercices, loading, error } = useQuery(GET_EXERCICES, {
    context: {
      headers: {
        Authorization: `Bearer ${token}`,
      },
    },
  });
  const filteredExercices = getFilteredExercices(rawExercices);

  if (loading) {
    return <Text>Loading...</Text>;
  }
  if (error) {
    return <Text>Error</Text>;
  }

  return (
    <View>
      <TextInput
        value={filterText}
        onChangeText={setFilterText}
        placeholder="筛选运动..."
      />
      {filteredExercices.map(exercise => (
        <Text key={exercise.id}>{exercise.name}</Text>
      ))}
    </View>
  );
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 16:48:31