Vue3+TypeScript下如何将Pinia Store作为组件Prop传递
我使用TypeScript、Pinia和Vue3开发项目,现有一个MenuButton组件,需要将管理菜单开闭状态及显示/隐藏动作的Pinia Store作为Prop传入。应用内多个菜单均通过同一工厂函数定义Store,目前正解决TypeScript类型适配问题。
初始代码与类型不匹配错误
Store定义代码(nav.store.ts)
import { defineStore } from "pinia"; import { useStorage } from "@vueuse/core"; import type { RemovableRef } from "@vueuse/core"; export interface MenuStore { isOpen: RemovableRef<boolean>, toggle(force?: boolean): void, open(): void, close(): void, } interface State { isOpen: RemovableRef<boolean>; } function menuStoreFactory(id: string) { return defineStore(id, { state: () : State => ({ isOpen: useStorage(`${id}-open`, false), }), actions: { toggle(force?: boolean) { this.isOpen = force != undefined ? force : !this.isOpen; }, open() { this.isOpen = true; }, close() { this.isOpen = false; } } }); } export const useMainMenuStore = menuStoreFactory('mainMenu'); export const useMobileMenuStore = menuStoreFactory('mobileMenu');
MenuButton组件Setup代码
import { MenuIcon, MenuLeftIcon } from "@/icons"; import type { MenuStore } from "@/modules/nav/nav.store"; interface Props { controller: MenuStore } const props = defineProps<Props>();
组件使用方式
<template> <MenuButton :controller="mainMenu" ></MenuButton> </template> <script setup lang="ts"> const mainMenu = useMainMenuStore(); </script>
此时出现Prop类型不匹配错误,原因是Pinia生成的Store实例包含额外内置属性,和自定义的MenuStore接口不兼容。
尝试继承PiniaCustomStateProperties的问题
修改MenuStore接口继承PiniaCustomStateProperties:
import type { PiniaCustomStateProperties } from 'pinia'; export interface MenuStore extends PiniaCustomStateProperties<{ isOpen: RemovableRef<boolean>, toggle(force?: boolean): void, open(): void, close(): void, }> {}
这种修改解决了组件使用时的类型错误,但组件内部访问props.controller.toggle()或props.controller.isOpen时,会提示toggle()和isOpen未定义。
尝试继承Store接口的报错
修改MenuStore接口继承Pinia的Store接口:
import type { Store } from 'pinia'; export interface MenuStore extends Store<string, { isOpen: RemovableRef<boolean>, toggle(force?: boolean): void, open(): void, close(): void, }> {}
此时组件内部无类型错误,但使用组件时出现类型不兼容报错:
Type
_StoreWithState<string, State, {}, {toggle(force?: boolean): void, close(): void, open(): void}> & UnwrapRef<State> & _StoreWithGetters<{}> & {toggle(force?: boolean): void, close(): void, open(): void} & PiniaCustomProperties<string, State, {}, {toggle(force?: boolean): void, close(): void, open(): void}> & PiniaCustomStateProperties<State>is not assignable to type MenuStore ...
TypePiniaCustomStateProperties<State>is not assignable to type MenuStore
正确解决方案
步骤1:拆分State和Actions类型
在nav.store.ts中单独定义State和Actions类型,明确边界:
import { defineStore } from "pinia"; import { useStorage } from "@vueuse/core"; import type { RemovableRef } from "@vueuse/core"; import type { Store } from 'pinia'; // 菜单状态类型 interface MenuState { isOpen: RemovableRef<boolean>; } // 菜单动作类型 interface MenuActions { toggle(force?: boolean): void; open(): void; close(): void; } // 通用MenuStore类型:Pinia Store实例,包含指定状态和动作 export type MenuStore = Store<string, MenuState, {}, MenuActions>;
步骤2:修正工厂函数返回类型
让工厂函数返回的defineStore结果明确对应MenuStore类型:
function menuStoreFactory(id: string) { return defineStore(id, { state: (): MenuState => ({ isOpen: useStorage(`${id}-open`, false), }), actions: { toggle(force?: boolean) { this.isOpen = force !== undefined ? force : !this.isOpen; }, open() { this.isOpen = true; }, close() { this.isOpen = false; }, }, }) as () => MenuStore; } export const useMainMenuStore = menuStoreFactory('mainMenu'); export const useMobileMenuStore = menuStoreFactory('mobileMenu');
步骤3:组件内使用修正后的类型
MenuButton组件的Props定义保持不变,现在类型可完全匹配:
import { MenuIcon, MenuLeftIcon } from "@/icons"; import type { MenuStore } from "@/modules/nav/nav.store"; interface Props { controller: MenuStore } const props = defineProps<Props>();
修改后,组件内部调用Store方法/访问状态、传递Store实例给组件时,均不会出现类型错误。
内容的提问来源于stack exchange,提问作者aron.duby

