Pinia中如何实现Store共享action与getter?是否为反模式?
实现方案
针对多个Pinia Store存在重复getter和action的场景,推荐两种符合DRY原则的实现方式:
1. 基础Store工厂函数
把通用的state、getter和action抽离到工厂函数中,具体媒体Store通过扩展工厂返回值复用逻辑,同时添加自身特有的内容。
基础工厂示例
// stores/baseMedia.js export function createBaseMediaStore() { return { state: () => ({ items: [] }), getters: { count: (state) => state.items.length, getById: (state) => (id) => state.items.find(item => item.id === id) }, actions: { deleteOne(id) { this.items = this.items.filter(item => item.id !== id) }, addOne(item) { this.items.push(item) } } } }
具体媒体Store示例(以书籍为例)
// stores/book.js import { defineStore } from 'pinia' import { createBaseMediaStore } from './baseMedia' export const useBookStore = defineStore('books', { // 扩展通用逻辑 ...createBaseMediaStore(), // 覆盖state并添加书籍特有属性 state: () => ({ ...createBaseMediaStore().state(), readingProgress: {} }), // 合并通用getter与书籍特有getter getters: { ...createBaseMediaStore().getters, finishedCount: (state) => state.items.filter(book => book.isFinished).length }, // 合并通用action与书籍特有action actions: { ...createBaseMediaStore().actions, updateReadingProgress(bookId, progress) { this.readingProgress[bookId] = progress } } })
2. 组合式API风格的Composable
如果项目采用Pinia的组合式API写法,可将通用逻辑封装成可复用的composable,在具体Store中导入使用。
通用逻辑Composable示例
// composables/useMediaStore.js import { ref, computed } from 'vue' export function useMediaStoreLogic() { const items = ref([]) const count = computed(() => items.value.length) const getById = (id) => items.value.find(item => item.id === id) function deleteOne(id) { items.value = items.value.filter(item => item.id !== id) } function addOne(item) { items.value.push(item) } return { items, count, getById, deleteOne, addOne } }
具体媒体Store示例(组合式写法)
// stores/book.js import { defineStore } from 'pinia' import { useMediaStoreLogic } from '../composables/useMediaStore' import { ref, computed } from 'vue' export const useBookStore = defineStore('books', () => { // 复用通用逻辑 const { items, count, getById, deleteOne, addOne } = useMediaStoreLogic() // 书籍特有状态 const readingProgress = ref({}) // 书籍特有计算属性 const finishedCount = computed(() => items.value.filter(book => book.isFinished).length) // 书籍特有方法 function updateReadingProgress(bookId, progress) { readingProgress.value[bookId] = progress } return { items, count, getById, deleteOne, addOne, readingProgress, finishedCount, updateReadingProgress } })
是否属于反模式?
这种复用通用逻辑的思路不是反模式,反而契合Pinia的设计理念——Pinia本身就鼓励通过组合、复用的方式减少重复代码。
需要注意的边界情况:
- 避免过度抽象:如果某类媒体的业务逻辑与通用逻辑差异极大,强行复用会增加代码复杂度,此时单独编写Store更合适。
- 保持职责单一:每个媒体Store只处理对应类型的数据,通用逻辑仅抽离公共部分,确保代码清晰可维护。
内容的提问来源于stack exchange,提问作者Daniel Richter
相关产品推荐
相关产品推荐

