Pinia/Vuex 以及 Redux 旨在成为“单一事实来源”,您可以在其中拥有一个或多个存储应用程序数据的商店,这些数据可从任何地方获得。
Pinia 商店看起来像这样:
export let useProductsStore = defineStore('products', () => {
let data = ref(products);
function getList (params) {
return someSearchStuffForProducts(params);
}
return {data, getList};
});
然后可以用作:
let productsStore = useProductsStore();
console.log(data, data.value);
productsStore.getList(params);
我们可以创建多个商店:
let usersStore = useUsersStore();
let productsStore = useProductsStore();
let basketStore = useBasketStore();
let favoritesStore = useFavoritesStore();
店铺可以互相参考:
export let useUsersStore = defineStore('users', () => {
let productsStore = useProductsStore();
}
export let useBasketsStore = defineStore('basket', () => {
let productsStore = useProductsStore();
}
//Et cetera
最后,Pinia/Vuex 是提供检索和操作存储在状态中的数据的能力的工具。
但是还有另一种方法,行之有效的方法:管理器/服务类。
前面的例子可以改写为:
//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);
所有这些都可以在 TypeScript 中轻松输入,无需额外的包装器、
ref()
等
据我所知,Pinia 看起来像是“重新发明轮子”:以笨拙的方式编写相同的功能。
此外,它不提供依赖注入:您不能在配置中初始化商店并准确地将一个商店注入另一个商店,您必须通过
useProductsStore()
等将依赖项硬编码到商店中。
继承或任何其他面向对象的东西也是不可能的。
Pinia 甚至 促进 循环依赖,这导致了意大利面条式的代码,可维护性很差。
那么,毕竟,为什么人们更喜欢 Pinia/Vuex 而不是经过实战检验的、带有管理器类的干净的 OOP 方法?我已经用了几十个小时来编写我自己发明的教程项目,使用 Pinia 作为“下一个推荐的 Vue 状态管理”,现在我很想将所有内容重写到管理器类中,因为我发现 Pinia 笨拙且废弃。我只记得几年前我正在编写另一个测试项目 - 使用 Vue2 - 当时我使用了管理器类 - 一切都很顺利。我忽略了什么吗?如果我放弃 Pinia 会有问题吗?
类是 Vue 反应性中的二等公民,并且有一些陷阱。他们不能在构造函数中绑定
this
,这将导致使用非反应性类实例而不是反应性代理。他们 无法有效地使用 refs ,因为这些 refs 以 记录但异常的方式展开 。他们不能对计算引用使用 get/set 访问器。这些问题要求要么通过显式使用 Vue 反应性 API 以奇怪的方式编写类,要么以受限的方式设计类,因此 reactive(new MyClass)
不会阻止它正常工作。
类没有商店所具有的功能,例如对 Vue 开发工具、插件系统等的广泛支持
依赖注入不是类独有的,可以以合适的方式执行,例如对于 Pinia 商店:
const basketManagerStore = defineStore({
state: () => ({ _getFoo: null }),
getters: {
foo: state => state._getFoo()
},
actions: {
setFoo(getFoo) {
this._getFoo = getFoo;
}
}
});
basketManagerStore.setFoo(useSomeFooStore);
在许多情况下,最好处理 Pinia 存储可组合项而不是存储实例,因为这解决了循环依赖性问题,如果过早调用可组合项可能会出现问题。同样的问题可能出现在类中,需要使用 DI 容器而不是直接使用类实例。
继承没有问题,因为可重用代码可以用 FP 而不是 OOP 来处理。 Vue 没有明确地推广它,但使前者更加地道和舒适。
TL;DR:坚持普通对象和 FP,因为这是 Vue 反应性设计的主要情况。