Pinia/Vuex
Pinia/Vuex, as well as Redux, are designed to be a "single source of truth", where you have a store or multiple stores that hold the application data, available from everywhere.
A Pinia store looks like this:
export let useProductsStore = defineStore('products', () => {
let data = ref(products);
function getList (params) {
return someSearchStuffForProducts(params);
}
return {data, getList};
});
Then can be used as:
let productsStore = useProductsStore();
console.log(data, data.value);
productsStore.getList(params);
We can create multiple stores:
let usersStore = useUsersStore();
let productsStore = useProductsStore();
let basketStore = useBasketStore();
let favoritesStore = useFavoritesStore();
Stores can refer to each other:
export let useUsersStore = defineStore('users', () => {
let productsStore = useProductsStore();
}
export let useBasketsStore = defineStore('basket', () => {
let productsStore = useProductsStore();
}
//Et cetera
In the end, Pinia/Vuex are tools that provide abilities to retrieve and manipulate data stored in states.
Manager/service classes
But there's another approach, well-established one: manager/service classes.
Previous examples can be rewritten as:
//Define the "single source of truth"
let store = {
products: { /* ... */},
currentUser: { /* ... */},
userBasket: { /* ... */},
userFavorites: { /* ... */},
};
//Here goes manager classes
class ProductsManager {
constructor (params) {
this.state = params.state;
//...
}
getList (params) {
return someSearchStuffForProducts(params);
}
}
class UsersManager {
constructor (params) {
this.state = params.state;
//Products manager is injected as a dependency
this.productsManager = params.productsManager;
//...
}
}
class BasketManager {
constructor (params) {
this.state = params.state;
//Products manager is injected as a dependency
this.productsManager = params.productsManager;
//...
}
}
//Some config/initialization script
export let DIC = {}; //Container for manager instances
DIC.productsManager = new ProductsManager({state: store.products});
DIC.usersManager = new usersManager({
state: store.currentUser,
productsManager: DIC.productsManager,
});
DIC.basketManager = new BasketManager({
state: store.userBasket,
productsManager: DIC.productsManager,
});
//Usage
import {DIC} from './config';
DIC.productsManager.getList();
DIC.basketManager.add(someProductId);
DIC.basketManager.changeCount(someProductId, 3);
All of this can be easily typed in TypeScript without additional wrappers, ref()
, etc.
Discussion
As far as I can see, Pinia looks like "reinventing the wheel": same functionality written in a clumsy way.
Moreover, it doesn't provide dependency injection: you can't init stores in the config and accurately inject one store to another, you have to hardcode dependencies right into a store, by useProductsStore()
and such.
Inheritance or any other OOP stuff aren't possible too.
Pinia even promotes circular dependencies, which leads to spaghetti code with poor maintainability.
So, after all, why should one prefer Pinia/Vuex over battle-tested, clean OOP approach with manager classes? I've been writing my self-invented tutorial project for dozens of hours, using Pinia as "next recommended state management for Vue", and now I feel tempted to rewrite everything into manager classes, as I find Pinia unwieldy and abundant. I just recalled that several years ago I was writing another test project - with Vue2 - I used manager classes then - and everything was working smoothly. Do I overlook something? Will I have problems if I abandon Pinia?