本地与共享状态

共 23 题
📑 题目列表 23 题
#
★★★

1. useReducer + Context 在中型状态管理的边界(与 Redux 的取舍)

请分析 useReducer + Context 组合在中型应用状态管理中的适用边界,并说明它与 Redux 的取舍?

  • useReducer + Context 的适用规模与局限性
  • 与 Redux 在性能、工具链、可维护性上的对比
  • 何时该迁移到正式状态库

useReducer + Context 通过 useReducer 管理"状态+更新逻辑",用 Context 把状态与 dispatch 下发到组件树。它适合状态规模中等、层级浅、更新频率不高的场景,而不引入额外依赖。但 Context 的局限在于:value 变化会使所有消费该 Context 的组件重渲染(即使只依赖其中一部分),缺乏细粒度订阅;且没有中间件、DevTools 时间旅行、持久化等工具链。Redux 则通过订阅机制和 selector 规避了重渲染问题,并提供 DevTools、middleware、持久化等完备生态。取舍的关键是规模与复杂度:小型到中型、状态树扁平、更新不频繁时 useReducer+Context 足够;需要跨模块共享、复杂异步、强调试能力时选 Redux。

核心边界在于"重渲染粒度"与"工程能力"。Context 的 value 变化是整树通知,而 Redux 可精确到单个订阅者。当状态分散、更新频繁、或需要严格的单向数据流治理时,Redux 的收益才超过其样板代码成本。

const CountContext = createContext(null);
function reducer(state, action) {
  switch (action.type) {
    case 'inc': return { ...state, count: state.count + 1 };
    default: return state;
  }
}
function CountProvider({ children }) {
  const [state, dispatch] = useReducer(reducer, { count: 0 });
  return <CountContext.Provider value={{ state, dispatch }}>{children}</CountContext.Provider>;
}
#
★★★

2. Nanostores 的 @nanostores/react 与跨框架互操作

请解释 Nanostores 的 @nanostores/react 绑定,以及它如何实现跨框架互操作?

  • Nanostores 的 store 概念与 atom
  • @nanostores/react 的 useStore 绑定
  • 框架无关的订阅机制

Nanostores 是一个极小的、框架无关的状态库,核心是 store(atom、map、computed)。它把状态与订阅机制与框架解耦,任何 Store 都可以通过 subscribe 监听。@nanostores/react 提供 useStore 钩子把 store 接入 React 组件,组件挂载时订阅、卸载时取消。由于 store 本身不依赖 React,同一份 store 可以在 React、Vue、Svelte、Solid 甚至原生 JS 中共享,实现跨框架互操作。这使它适合微前端、多框架共存或需要把业务状态抽离 UI 的场景。

Nanostores 的设计哲学是"最小存储 + 框架适配层":核心只有几十行订阅逻辑,框架绑定只是薄封装。互操作的价值在于状态与 UI 框架解耦,一份状态可被多种技术栈消费。

// store.js
import { atom } from 'nanostores';
export const count = atom(0);

// React 组件
import { useStore } from '@nanostores/react';
import { count } from './store';
function Counter() {
  const value = useStore(count);
  return <button onClick={() => count.set(value + 1)}>{value}</button>;
}
#
★★★

3. Context API + useSyncExternalStore 在轻量全局状态的工程价值

请解释 Context API 与 useSyncExternalStore 在轻量全局状态管理中的工程价值?

  • useSyncExternalStore 的签名与作用
  • 解决 tearing 问题与外部 store 接入
  • 轻量全局状态的选型

useSyncExternalStore 是 React 18 提供的从外部 store 读取状态的 Hook,签名是 useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)。它让 React 能够安全地订阅外部 store(如 Redux、Zustand、自定义 store),并在并发渲染下保证一致性,避免 tearing(同一状态在不同组件渲染出不同值)。相比 Context 的整树通知,useSyncExternalStore 可以实现精确订阅,只有快照变化时组件才重渲染。工程价值在于:无需引入完整状态库,就能用一小段自定义 store + useSyncExternalStore 实现轻量、高性能的全局状态,且与 React 并发特性兼容。

Context 适合"低频、粗粒度"的依赖注入,useSyncExternalStore 适合"高频、细粒度"的订阅。两者结合(Context 传 store 实例,组件用 useSyncExternalStore 订阅)是轻量全局状态的标准做法。

const store = createStore(0); // 自定义外部 store
function useCount() {
  return useSyncExternalStore(store.subscribe, store.getSnapshot);
}
#
★★★

4. Zustand 与 Jotai 在原子状态(atomic state)

请对比 Zustand 与 Jotai 在原子状态(atomic state)上的设计理念与工程差异?

  • Zustand 的外部 store + selector 订阅模式
  • Jotai 的原子组合与依赖追踪
  • 两者的适用场景

Zustand 采用"外部 store"模型:一个集中式 store,组件通过 selector 选择自己需要的部分,只有选中部分变化时才重渲染。Jotai 采用"原子状态"模型:每个独立状态(atom)是一个原子,原子之间通过依赖组合成派生状态,组件订阅最少的原子,diff 由框架自动计算。两者都依赖细粒度订阅,但心智模型不同:Zustand 是"一个大 store + 切片 selector",Jotai 是"无限小原子 + 组合"。Zustand 更接近传统全局 store,适合集中管理;Jotai 更分散、更接近组件局部状态的自然延伸,适合颗粒度高的状态依赖。

Zustand 的 selector 需要手动保证引用稳定(配合 useShallow),Jotai 的依赖追踪是自动的。Jotai 天然支持按需派生、懒惰初始化,Zustand 更强调集中与可扩展中间件。选型取决于团队偏好与状态组织方式。

#
★★★

5. Jotai 的 atomFamily 与 atomWithStorage 在动态键与持久化的应用

请解释 Jotai 的 atomFamily 与 atomWithStorage 在动态键与持久化场景中的应用?

  • atomFamily 的动态原子创建与参数化
  • atomWithStorage 的持久化封装
  • 两者结合管理大量同构状态

atomFamily 用于根据参数批量创建原子:atomFamily(param => atom(init)),它缓存每个参数对应的原子,适合管理"多个同构实体"的状态(如按 id 维护的列表项)。atomWithStorage 把原子与 localStorage/sessionStorage 绑定,自动读写并跨标签同步,返回值是原子,可像普通 atom 使用。两者结合可管理"按 id 持久化"的实体状态,例如每个用户的偏好设置。atomFamily 还支持参数序列化与清理,避免内存泄漏。

atomFamily 解决"动态键"问题,避免为每个 id 手写原子;atomWithStorage 解决"持久化"问题,把 storage 读写封装成原子。二者结合让"大量参数化 + 需要持久化"的状态管理变得声明式。

import { atomFamily, atomWithStorage } from 'jotai/utils';
const itemAtom = atomFamily((id) => atomWithStorage(`item:${id}`, { title: '' }));
// 使用
const current = useAtom(itemAtom('abc'));
#
★★★

6. Zustand 的 subscribeWithSelector 与 useShallow 在 selector 引用稳定性的工程价值

请解释 Zustand 的 subscribeWithSelector 中间件与 useShallow 在 selector 引用稳定性上的工程价值?

  • subscribeWithSelector 的按字段订阅
  • useShallow 的浅比较防重渲染
  • selector 引用稳定的重要性

Zustand 默认的 selector 使用 Object.is 比较结果,当 selector 返回对象/数组时每次都会产生新引用,导致不必要的重渲染。subscribeWithSelector 中间件让 store.subscribe 支持按 selector 字段订阅,回调只在选中字段变化时触发。useShallow 是浅比较版的 selector 封装,当返回的顶层字段逐个比较都相等时判定结果未变,避免因新对象引用而重渲染。工程价值在于:选择多个字段时(如 {a, b})无需每次创建新对象,显著减少重渲染,同时保持 API 简洁。

selector 引用稳定性是 Zustand 性能的关键。useShallow 用浅比较把"新对象但内容相同"识别为未变化,从而避免多余渲染;深层结构仍建议用自定义 compare 或 memoize。

import { shallow } from 'zustand/shallow';
const { a, b } = useStore(useShallow((s) => ({ a: s.a, b: s.b })));
#
★★★

7. Jotai 的原子化状态与依赖追踪

请解释 Jotai 的原子化状态模型与依赖追踪机制?

  • atom 的 primitive 与 derived 类型
  • 依赖图与自动追踪
  • 组件最小订阅

Jotai 中每个状态都是一个 atom,分为 primitive atom(持有值)和 derived atom(由其他 atom 计算得出,通过 get 读取依赖)。框架维护一张依赖图,当某个 atom 变化时,自动重算依赖它的 derived atom,并通知订阅了受影响 atom 的组件。依赖追踪是"按需"的:组件只订阅自己读到的 atom,且只在该 atom 实际变化时重渲染,实现最小化更新。Jotai 还支持异步 atom(async atom)与面向并发渲染的优化。

Jotai 的依赖追踪本质上是细粒度响应式 + 惰性求值:derived atom 只有被读取时才计算,计算结果被缓存,依赖变化时按图传播。这让状态组织更模块化,避免手动管理 selector 与订阅。

const count = atom(0);
const doubled = atom((get) => get(count) * 2);
function View() {
  const [value] = useAtom(doubled); // 只订阅 doubled 相关依赖
  return <div>{value}</div>;
}
#
★★★

8. Zustand 的轻量与 selector 订阅

请解释 Zustand 的轻量设计与 selector 订阅机制?

  • Zustand 的极小体积与无 Provider 结构
  • selector 订阅与重渲染控制
  • 与 Context 的性能对比

Zustand 的核心原理是"外部 store + 订阅",没有 Provider 包裹,store 是模块级单例,组件通过 useStore(selector) 订阅。selector 决定组件读取哪些状态,store 内部用 Object.is 比较 selector 结果,只有变化时才触发重渲染。相比 Context 的整树通知,Zustand 可实现精确到字段的订阅,减少无关重渲染。库体积仅约 1KB,无框架耦合,且支持中间件(persist、devtools、subscribeWithSelector 等),适合作为轻量全局状态层。

Zustand 的"无 Provider"降低心智负担,selector 订阅是性能关键。它把状态管理精简为"读-订阅-写"三个原语,同时保留中间件扩展能力,是生态与简洁的平衡。

import { create } from 'zustand';
const useStore = create((set) => ({
  count: 0,
  inc: () => set((s) => ({ count: s.count + 1 })),
}));
const count = useStore((s) => s.count); // 仅订阅 count
#
★★★

9. useState/useReducer 与外部状态管理库的边界

请说明 useState/useReducer 与外部状态管理库(Redux/Zustand 等)的边界划分?

  • 组件局部状态 vs 全局状态
  • 状态提升与 props 钻透
  • 何时引入外部库

useState/useReducer 是组件局部状态,适合仅在组件内使用、不需要跨组件共享的状态。当状态需要被多个组件共享时,通常先"状态提升"(lift state up)到共同父组件,通过 props 传递。当状态提升导致 props 钻透(prop drilling)、或状态需要跨大量组件/跨路由模块共享、或需要复杂异步与调试能力时,外部状态库的价值才显现。外部库(Redux/Zustand/Jotai)提供全局订阅、细粒度更新、DevTools、持久化等能力,但引入额外依赖与心智成本。边界原则:能用局部状态解决的不要全局化,能用状态提升解决的不要引入库。

边界由"共享范围"与"复杂度"决定。局部状态天然最优,状态提升是中间的升级路径,外部库是处理大规模共享与复杂逻辑的最终手段。选择应遵循"够用就好",避免过早抽象。

#
★★★

10. TanStack Query 的 staleTime/gcTime 与 React 19 use()/Server Functions 的协作

请解释 TanStack Query 的 staleTime/gcTime 配置,以及它与 React 19 的 use() 和 Server Functions 的协作?

  • staleTime 与 gcTime 的区别
  • React 19 use() 读取 promise/context
  • Server Functions 与前端数据获取的配合

staleTime 决定数据"多久后视为过期",过期前读取走缓存不重新请求;gcTime 决定缓存数据在不再被引用后多久被垃圾回收(默认 5 分钟)。两者共同管理缓存生命周期:staleTime 控制刷新频率,gcTime 控制内存释放。React 19 的 use() 可直接在组件中读取 promise(配合 Suspense),Server Functions 则允许在客户端调用服务端逻辑。协作方式:把"服务端数据获取"封装为 Server Function,返回的 promise 用 use() 读取,而 TanStack Query 负责把结果缓存、标记 stale、自动 revalidate,避免重复请求并保证数据新鲜度。

staleTime 偏"刷新策略",gcTime 偏"内存策略"。React 19 的 use()/Server Functions 解决"如何取数",TanStack Query 解决"如何缓存与失效",二者互补:前者聚焦数据来源,后者聚焦缓存生命周期。

#
★★★

11. Zustand 的 slice 与持久化 与 SSR/RSC 边界的协作

请解释 Zustand 的 slice 模式与持久化能力,以及它在 SSR/RSC 边界下的协作策略?

  • slice 模式拆分 store
  • persist 中间件持久化
  • SSR/RSC 下的 hydration 与 singleton 问题

Zustand 的 slice 模式把一个大 store 拆成多个"切片"(每个切片是一段 state+action),再通过 create 合并,便于模块化与类型推导。persist 中间件把 store 状态持久化到 storage(localStorage/sessionStorage),并在初始化时 rehydrate。SSR/RSC 边界下,关键是避免把服务端数据写入客户端 singleton:模块级 store 是单例,在 SSR 中可能被多个请求共享导致串数据。解法是 hydration 时用服务端数据填充 store,或使用 createStore 按请求创建 store 实例。Zustand 还支持 persist 的 skipHydration 与手动 rehydrate 以配合 SSR。

slice 解决"代码组织",persist 解决"持久化",SSR 边界解决"单例隔离"。RSC 下客户端组件状态与服务端组件数据流分离,persist 的 localStorage 只在浏览器端生效,需在 hydration 后启用。

#
★★

12. Valtio 的 proxy/snapshot/subscribe 双向同步在表单与撤销重做的应用

请解释 Valtio 的 proxy/snapshot/subscribe 双向同步机制,及其在表单与撤销重做中的应用?

  • proxy 的响应式代理
  • snapshot 与 subscribe
  • 借助 useSnapshot 实现表单与撤销重做

Valtio 用 Proxy 把普通对象变成响应式状态,直接修改 proxy 属性即可更新状态。useSnapshot 返回一个不可变快照并订阅变化,组件用快照渲染,写操作通过 proxy 完成,实现"读快照、写 proxy"的双向同步。表单应用:直接绑定快照字段到输入框,onChange 更新 proxy 对应属性,无需手写大量 setState。撤销重做:由于 proxy 修改可被记录,可维护历史栈(每次修改前保存快照),配合 subscribe 记录变更,实现 undo/redo。

Valtio 的亮点是"可变写 + 不可变读":写体验像普通对象,读体验符合 React 不可变渲染。快照机制让历史记录(撤销重做)天然可行——每次 commit 保存一份快照。

import { proxy, useSnapshot } from 'valtio';
const state = proxy({ form: { name: '' } });
function Form() {
  const snap = useSnapshot(state);
  return <input value={snap.form.name} onChange={(e) => (state.form.name = e.target.value)} />;
}
#
★★

13. Nanostores 的极小体积与框架无关

请解释 Nanostores 的极小体积与框架无关设计?

  • 体积优势(约 1KB)
  • 无框架依赖的订阅机制
  • 跨框架与微前端共享

Nanostores 的核心只有约 1KB,因为它只实现最基础的 store 原语(atom/map/computed)与订阅函数,不依赖任何框架。订阅通过 store.subscribe 回调实现,框架绑定(如 @nanostores/react、@nanostores/vue)是独立的薄封装。这种框架无关设计让同一份 store 可在 React、Vue、Svelte、Solid 甚至原生 JS 中共享,非常适合微前端、组件库或需要把领域状态与 UI 解耦的场景。

极小体积意味着更快的加载与更低的依赖污染,框架无关意味着更广的复用面。代价是缺少高阶能力(如中间件、DevTools),需自行组合。

#
★★

14. Valtio 的 Proxy 响应式模式

请解释 Valtio 基于 Proxy 的响应式模式?

  • Proxy 拦截读写
  • useSnapshot 的订阅与快照
  • 与 React 不可变渲染的衔接

Valtio 用 Proxy 深度代理状态对象,读操作被追踪(用于依赖收集),写操作触发订阅通知。useSnapshot 在组件内读取状态,返回最新快照并订阅相关字段,字段变化时组件重渲染。它把"可变式写"与"不可变式读"结合:开发者像操作普通对象一样改状态,React 却看到不可变快照,符合 React 的渲染模型。响应式是自动的,无需手动 selector。

Proxy 响应式的核心是"get 时收集依赖、set 时触发通知"。useSnapshot 利用 Proxy 的 get 追踪组件实际读取的字段,实现细粒度订阅,无需手动声明依赖。

#
★★

15. Redux Toolkit 在测试与可维护性的工程价值

请分析 Redux Toolkit 在测试与可维护性方面的工程价值?

  • 纯函数 reducer 的可测试性
  • createSlice/createAsyncThunk 的结构化
  • 可维护性与团队协作

Redux Toolkit(RTK)通过 createSlice 把 state、reducer、action 组织在一起,reduce 是纯函数,输入 state 与 action 输出新 state,无副作用,因此极易单元测试。createAsyncThunk 把异步流程封装为 pending/fulfilled/rejected 三种 action,测试时可 mock 异步操作并断言状态流转。可维护性上,slice 的模块化让状态变更点集中、可搜索,配合 Immer 简化不可变更新,降低手写错误。整体上 RTK 降低了样板代码,让状态变更逻辑可预测、可测试、可审查。

工程价值在于"纯函数 + 结构化 + 可测试"。Reducer 纯函数是测试的基石,createSlice 的集中式定义为可维护性提供保障,createAsyncThunk 让异步状态流转可断言。

#
★★

16. TanStack Query v5 与 URL 状态/路由器的协作

请说明 TanStack Query v5 与 URL 状态/路由器的协作方式?

  • queryKey 与 URL 参数映射
  • Prefetch 与路由预加载
  • 缓存与 URL 同步

TanStack Query v5 与 URL 状态协作的核心是把 URL 参数作为 queryKey 的一部分,使 URL 变化触发查询重新获取或使用缓存。例如把分页、搜索词、筛选条件写入 queryKey,路由切换时自动取数或命中缓存。配合路由 prefetch(如 TanStack Router 的 loader 或 React Router 的 loader),可在导航前预取数据并写入缓存。URL 作为"可分享的请求状态",queryKey 作为"缓存标识",两者的一致性保证同一 URL 对应同一份缓存。

协作关键是把"URL 状态"归一化为"queryKey",让缓存与路由天然对齐。v5 弱化了对象引用,用字符串化 queryKey 增加缓存稳定性。

#
★★

17. Recoil(已停止维护)历史方案与 Jotai 演进的取舍

请分析 Recoil(已停止维护)作为历史方案,与 Jotai 演进的取舍?

  • Recoil 的 atom/selector 模型
  • Recoil 停止维护的原因
  • Jotai 的演进与替代

Recoil 由 Meta 开发,提出 atom(原子状态)与 selector(派生状态)的概念,用 useRecoilState 管理,理念上领先。但项目长期缺乏维护、更新缓慢、与 React 版本演进脱节,最终停更。Jotai 继承了"原子化状态"思想,但更轻量、API 更简洁、演进出 atomFamily/atomWithStorage 等实用工具,且保持活跃维护。取舍上:存量 Recoil 应用需迁移,atom/selector 概念可平滑映射到 Jotai;新项目应选择活跃维护的 Jotai。

取舍核心是"生态活力"与"概念迁移成本"。Recoil 的原子模型被 Jotai 继承并改进,迁移时把 Recoil 的 atom/selector 换成 Jotai 的 atom,即可复用大部分心智模型。

#
★★

18. useRef/useImperativeHandle 在命令式 API(动画控制器)

请解释 useRef 与 useImperativeHandle 在命令式 API(如动画控制器)中的应用?

  • useRef 持有可变引用
  • forwardRef 与 useImperativeHandle 暴露命令式方法
  • 动画控制器等场景

useRef 提供一个跨渲染持久的可变对象,用于持有 DOM 节点或命令式实例。useImperativeHandle 配合 forwardRef,让父组件通过 ref 调用子组件暴露的命令式方法,而不是通过 props 传递状态。典型场景是动画控制器:子组件暴露 play()/pause()/reset() 等方法,父组件通过 ref 调用,实现命令式控制而非声明式状态同步。这适合动画、播放器、焦点管理、第三方非受控库的封装。

命令式 API 适合"动作调用"而非"状态同步"的场景。useImperativeHandle 把内部实现细节封装,父组件只依赖稳定的方法签名,避免因状态变化导致的多余重渲染。

const Player = forwardRef((props, ref) => {
  useImperativeHandle(ref, () => ({
    play: () => anim.play(),
    pause: () => anim.pause(),
  }));
  return <div ref={animRef} />;
});
#
★★

19. Signals 提案的细粒度响应式(alien-signals、@preact/signals)

请解释 Signals 提案的细粒度响应式,以及 alien-signals、@preact/signals 等实现?

  • Signal 的基本概念
  • 细粒度依赖追踪
  • 各框架的 Signals 实现

Signal 是一个可变的、可订阅的值容器,读取时被追踪依赖,写入时触发订阅者更新。它实现了细粒度响应式:组件只订阅真正读取的 signal,而非整个对象,从而避免整树重渲染。@preact/signals 在 Preact 中提供 useSignal 等 API,alien-signals 则是一个更底层的信号库,被很多框架借鉴。Signals 的核心优势是极高性能的细粒度更新,跳过虚拟 DOM diff,直接精确更新受影响部分。

Signals 的"get 追踪、set 通知"是最细粒度的响应式原语。相比 React 的整树协调,Signals 天然按需更新,与并发渲染结合可进一步优化。TC39 有 Signals 提案标准化进程。

#
★★

20. Signals 提案与 use-signals 的标准化进程

请说明 Signals 提案的标准化进程与 use-signals 的作用?

  • TC39 Signals 提案
  • use-signals 的 React 绑定
  • 标准化的意义

TC39 正在推进 Signals 提案,试图把"信号"这一细粒度响应式原语标准化为 JavaScript 语言特性,避免各框架各自实现。use-signals 是把 Signals 接入 React 的适配库,提供 useSignal 等 Hook,让 React 组件订阅信号并细粒度更新。标准化进程的意义在于:统一信号 API、促进跨框架互操作、降低学习成本,同时让工具链(DevTools、类型)能够标准化支持。

标准化仍处于 Stage 讨论阶段,尚未定稿。use-signals 等库是"提案驱动的实践",让开发者提前体验信号 API,同时也为提案提供反馈。

#

21. Recoil 停止维护后,atom/selector 模式向 Jotai 迁移的等价实现与踩坑

请说明 Recoil 停止维护后,atom/selector 模式向 Jotai 迁移的等价实现与常见踩坑?

  • Recoil atom/selector 到 Jotai 的映射
  • 异步 selector 与 selectorFamily 的迁移
  • 迁移的常见坑

Recoil 的 atom 对应 Jotai 的 atom,selector 对应 Jotai 的 derived atom(get 函数),selectorFamily 对应 atomFamily。Recoil 的 useRecoilState 对应 Jotai 的 useAtom,useRecoilValue 对应 useAtomValue。踩坑点:一是 Recoil 的 selector 有缓存语义,Jotai 的 derived atom 也缓存但需注意依赖变化时的重算;二是 Recoil 提供 atomFamily 的 URL 序列化,Jotai 需用 atomFamily 参数序列化;三是 Recoil 的 effects 与 selector 的订阅行为不同,迁移时需验证异步与派生逻辑。

概念映射基本 1:1,但 API 细节(缓存、序列化、effect)需要逐项核对。迁移时建议先抽出纯逻辑,再逐组件替换,最后验证派生与异步行为一致。

#

22. Apollo Client 的 normalized cache(typePolicies 与 field policies)在本地状态与远端数据协作的工程取舍

请解释 Apollo Client 的 normalized cache(typePolicies 与 field policies)在本地状态与远端数据协作中的工程取舍?

  • normalized cache 的归一化存储
  • typePolicies 与 field policies
  • 本地状态与远端数据的协作

Apollo Client 用 normalized cache 把 GraphQL 查询结果按类型+id 归一化存储,避免重复数据。typePolicies 定义类型级别的缓存行为(如 keyFields 指定主键),field policies 定义字段级别的读写(read/merge)与缓存策略。本地状态与远端数据协作时,可用本地字段(local-only fields)或 reactive variables 把 UI 状态与缓存数据结合。取舍:normalized cache 带来强一致性,但配置复杂、学习成本高;简单场景下 Query 库(TanStack Query)更轻量。Apollo 适合重度 GraphQL 应用。

normalized cache 的取舍在于"一致性与复杂度"的平衡。typePolicies/field policies 提供精细控制,但需要理解缓存重建规则;本地状态(reactive variables)与缓存数据可共存,实现远端数据与 UI 状态的统一。

#

23. TanStack Query 的 queryKey 与缓存失效 在大型表单与复杂状态的协作

请说明 TanStack Query 的 queryKey 与缓存失效在大型表单与复杂状态中的协作?

  • queryKey 的序列化与唯一性
  • invalidateQueries 失效策略
  • 表单提交后刷新缓存

queryKey 是缓存标识,由数组组成,可含对象,序列化后决定缓存唯一性。大型表单中,提交后需让相关查询失效(invalidateQueries)以重新拉取最新数据;也可用 setQueryData 乐观更新缓存。queryKey 的分层(如 ['posts', { id }])让失效可按前缀精确匹配,避免过度刷新。协作上,表单提交触发 mutation,成功后 invalidate 对应 queryKey,使列表/详情重新获取,保证缓存与服务器一致。

queryKey 是"缓存寻址",invalidateQueries 是"失效寻址"。按前缀匹配让失效范围精确可控,是复杂状态下避免缓存不一致的关键。