8.1 核心概念
- 自动类型推断
- Pinia 从 state/getters/actions 的实现自动推断出 store 的完整类型,无需辅助泛型。
- state 类型标注
- 初值为
null/空数组时需用as断言,否则会被推断成null/never[]。 - getter 返回类型
- 引用其它 getter(用 this)时应显式标注返回类型,避免循环推断报错。
- StoreGeneric / 类型工具
- Pinia 导出
ReturnType<typeof useXxxStore>等模式,用来标注函数参数中的 store。 - PiniaCustomProperties
- 用于声明插件注入属性的接口,让扩展属性获得类型提示。
- 接口优先
- 把 state 抽成
interface再作为 state 返回类型,是复杂 store 的推荐实践。
8.2 自动推断已经足够
绝大多数情况下什么都不用标注,Pinia 已推断出正确类型:
export const useCounterStore = defineStore("counter", {
state: () => ({ count: 0, name: "pinia" }),
getters: {
doubled: (state) => state.count * 2, // number(推断)
},
actions: {
setName(n: string) { this.name = n; },
},
});
const s = useCounterStore();
s.count; // number
s.doubled; // number
s.setName(1); // ❌ TS 报错:期望 string
8.3 为 state 显式标注类型
当初值无法推断出想要的类型时(如 null、空数组),用 as 或接口标注:
interface User { id: number; name: string; }
interface UserState {
currentUser: User | null;
list: User[];
}
export const useUserStore = defineStore("user", {
// 用接口约束整个 state 形状
state: (): UserState => ({
currentUser: null,
list: [],
}),
});
不标注的坑
state: () => ({ user: null }) 会把 user 推断成 null 类型,之后赋值对象就报错;list: [] 会被推断成 never[]。给 state 一个 interface 返回类型是最稳妥的做法。
8.4 getter 显式返回类型
getters: {
total: (state) => state.list.length, // 自动推断 number
// 引用其它 getter → 用 this → 标注返回类型
summary(): string {
return `共 ${this.total} 人`;
},
},
8.5 组合式 Store 的类型
export const useUserStore = defineStore("user", () => {
// 泛型 ref 明确类型
const currentUser = ref<User | null>(null);
const list = ref<User[]>([]);
const total = computed(() => list.value.length);
function add(u: User) {
list.value.push(u);
}
return { currentUser, list, total, add };
});
8.6 抽取 Store 类型
要在工具函数或组件 props 里接收一个 store,用 ReturnType 提取它的类型:
// 提取 store 实例类型
type UserStore = ReturnType<typeof useUserStore>;
// 函数接收 store 作为参数
function logUser(store: UserStore) {
console.log(store.currentUser?.name);
}
8.7 泛型 Store 工厂
需要为多种实体生成结构相同的 store(如列表 CRUD)时,可写一个泛型工厂:
function createListStore<T extends { id: number }>(name: string) {
return defineStore(name, () => {
const items = ref<T[]>([]);
function add(item: T) { items.value.push(item); }
function removeById(id: number) {
items.value = items.value.filter((i) => i.id !== id);
}
return { items, add, removeById };
});
}
// 使用
export const useProductStore = createListStore<Product>("products");
8.8 类型安全最佳实践
| 实践 | 理由 |
|---|---|
| state 用 interface 标注返回类型 | 避免 null/never[] 推断坑 |
| getter 引用 this 时标注返回类型 | 防止循环推断报错 |
| 用 ReturnType 抽取 store 类型 | 函数/props 传递 store 更安全 |
| 插件属性用 PiniaCustomProperties 声明 | 获得类型提示 |
| 开启 strict / noImplicitAny | 让 Pinia 推断发挥最大价值 |
对比 Vuex
Vuex 要给 store 写一堆 ModuleTree、ActionContext 泛型才能勉强类型化。Pinia 几乎零配置——这正是它取代 Vuex 的最大理由之一。
8.9 小结与下一步
- Pinia 自动推断已覆盖大多数场景,无需辅助泛型。
- state 初值为 null/空数组时用
interface返回类型标注最稳。 - 用
ReturnType<typeof useXxxStore>抽取 store 类型;泛型工厂批量造 store。 - 下一章讲 SSR 与 Nuxt——服务端注水与跨请求状态隔离。