Inheritance/Shared Operations and Getters in Pinia
P粉662614213
P粉662614213 2023-12-22 13:50:12
0
2
512

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粉662614213
P粉662614213

reply all(2)
P粉420958692

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粉449281068

This can be achieved using plugins docs

Sample movie:

You have multiple stores, each state using a shared naming scheme:

  • Project: Single entity project (single movie details)
  • collection: item collection (collection of all movies)

Each store will have the same CRUD operation, just the URL changes

  • getCollection: Get a list of items from the API and set the response to a collection (https://url.com/movies a>)
  • getItem: Get a single item from the API and set the response to the item (https://url.com/movies/ id)
  • handleError: Display alert with error message to user

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

Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template