I have several Pinia stores that are supposed to share a set of actions and getters, but I'm not quite sure how to implement this efficiently.
I'm building an application that allows users to manage many different media (books, movies, TV shows, etc.). The way I'm currently thinking about it is to have a store for each media type, such as BookStore, MovieStore, etc. Many getters and operations (such as count
and deleteOne
) are identical between these different stores.
How to implement DRY here? The examples in the Pinia documentation focus on reusing actions and getters within other stores, but I don't think this fully addresses my use case of directly inheriting a set of getters and setters.
Is the inheritance approach I'm trying here an anti-pattern?
P粉4209586922023-12-23 12:54:52
If you want some functionality to be shared across not all stores, you can use composable.
You can create a separate composable function and pass part of the store instance into it.
I made an example for you on codesandbox.
Here is a short example of codesandbox:
common.ts
import { computed, Ref, ref } from "vue"; export function useCommon(initValue: number) { const _value = ref<number>(initValue); function increment() { _value.value++; } function someProcessing() { // ... some code here } return { counter, increment, someProcessing, }; }
Then in any store you can use it like this:
fooStore.ts
export const useFooStore = defineStore('foo', () => { const state = ref<string>('foo'); const { counter, increment, someProcessing } = useCounter(0); return { state, counter, increment, someProcessing, } }
This way you can compose any function, object, etc. in any storage or in any component.
P粉4492810682023-12-23 00:43:42
This can be achieved using plugins docs
Sample movie:
You have multiple stores, each state using a shared naming scheme:
Each store will have the same CRUD operation, just the URL changes
Create plugin:
function BaseStorePlugin () { return { collection: [], item: {}, getCollection: function (url) { api.get(url) .then((response) => { this.collection = response.data; }) .catch((error) => { this.handleError(error); }); }, getItem: function (url) { api.get(url) .then((response) => { this.item = response.data; }) .catch((error) => { this.handleError(error); }); }, handleError: function (error) { window.alert(error); }, }; }
Provide plug-ins for Pinia:
const pinia = createPinia(); pinia.use(BaseStorePlugin);
Example movieStore.js (using shared actions and state)
import { defineStore } from 'pinia'; import { api } from 'src/boot/axios'; export const useMovieStore = defineStore({ id: 'movie', state: () => ({ movieSpecificStateObject: {}, }), actions: { movieSpecificAction (url) { console.log(this.item); api.get(url) .then((response) => { // handle response }) .catch((error) => { this.handleError(error); }); }, }, });
Usage examples in components
<template> <div v-for="movie in movieStore.collection" :key="movie.id" > <div> {{ movie.name }} </div> </div> </template> <script setup> import { onMounted } from 'vue'; import { useMovieStore } from 'src/stores/movieStore.js'; const movieStore = useMovieStore(); onMounted(() => { movieStore.readCollection('http://url.com/movies'); }); </script>
Editor: 1
If you pass the context into the plugin, you can access the store and the options passed into it, from which you can check the store ID and only return the specific store as shown below
function BaseStorePlugin (context) { const allowedStores = ['movie', 'album']; if (allowedStores.includes(context.store.$id)) { return { collection: [], getCollection: function () { const fakeCollection = Array.from({length: 10}, () => Math.floor(Math.random() * 40)); fakeCollection.forEach((item) => { this.collection.push({ id: item, name: `name${item}` }); }); }, }; }; }
I created a very basic example using 3 stores, the above check is available in codesandbox here< /a>