如何配置Zustand只读存储?TypeScript类型适配问题咨询
确保Zustand存储类型为只读的解决方案
1. 给根状态标记深度只读,从源头限制
直接在定义Zustand根状态类型时使用ReadonlyDeep,让整个状态树默认是深度只读的,避免在lens层面单独处理导致类型冲突:
import { ReadonlyDeep } from 'type-fest'; import { create } from 'zustand'; import { immer } from 'zustand/middleware/immer'; // 根状态直接标记为深度只读 type AppState = ReadonlyDeep<{ todos: { id: string; text: string }[]; filters: { status: 'all' | 'active' | 'completed' }; }>; const useStore = create<AppState>()( immer((set) => ({ todos: [], filters: { status: 'all' }, addTodo: (text) => set((draft) => { // Immer的draft是可变的,这里可以正常调用push等方法 draft.todos.push({ id: Date.now().toString(), text }); }), sortTodos: () => set((draft) => { // 即使在draft里用sort也没问题,但如果直接操作外部取出的state会报错 draft.todos.sort((a, b) => a.text.localeCompare(b.text)); }), })) );
这样定义后,直接从store取出的状态是只读的,比如const state = useStore(),此时调用state.todos.sort()会被TypeScript直接拦截,提前避免变异操作。
2. 让Lens自动继承根状态的只读性
不需要手动给Lens切片添加ReadonlyDeep,Zustand的Lens会自动从根状态推导类型,根状态是深度只读的话,切片自然也是只读的:
import { createLens } from 'zustand/lens'; // 自动推导得到只读的todos切片类型 const useTodosLens = createLens(useStore, (state) => state.todos); // 尝试调用变异方法会直接触发TS报错 const todos = useTodosLens(); todos.sort(); // ❌ TypeScript错误:sort方法会修改原数组,而todos是只读数组
3. 解决手动给Lens加ReadonlyDeep导致的TS问题
之前遇到的类型冲突,本质是手动给Lens指定ReadonlyDeep后,和Zustand Lens内部的类型推导逻辑冲突。正确的做法是完全依赖根状态的只读标记,让Lens自动继承类型,不要手动给切片加额外的只读修饰。
关键注意点
- Immer的
set回调参数是可变的draft对象,这里可以正常使用push、sort等变异方法,Immer会自动将其转换为不可变的状态更新。 - 从store直接取出的状态(非draft)是只读的,TypeScript会禁止任何变异操作,从语法层面提前拦截类似
sort的错误调用。
内容的提问来源于stack exchange,提问作者Francesco Meli
相关产品推荐
相关产品推荐

