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()
等等。
继承或任何其他 OOP 内容也是不可能的。
Pinia 甚至提倡循环依赖,导致意大利面条式代码,可维护性很差
那么,毕竟,为什么人们应该更喜欢 Pinia/Vuex 而不是久经考验的、带有管理器类的干净的 OOP 方法呢?我已经花了几十个小时编写我自己发明的教程项目,使用 Pinia 作为“下一个推荐的 Vue 状态管理”,现在我很想将所有内容重写为管理器类,因为我发现 Pinia 笨拙且丰富。我只是记得几年前我正在编写另一个测试项目 - 使用 Vue2 - 当时我使用了管理器类 - 一切都很顺利。我是否忽略了什么?如果我放弃 Pinia 会有问题吗?
类在 Vue 反应性中是二等公民,并且存在一些陷阱。它们无法在构造函数中绑定
this
,这将导致使用非反应式类实例反应式代理。他们无法有效地使用引用,因为这些引用是在记录但异常的方式。他们无法使用 get/set 访问器来计算引用。这些问题需要显式使用 Vue 反应性 API 以奇怪的方式编写类,或者以受限制的方式设计类,因此reactive(new MyClass)
不会阻止它工作正确。类不具备商店所具有的功能,例如对 Vue 开发工具、插件系统等的广泛支持。
类在 JavaScript 中也无法序列化,因此保存和恢复状态需要自定义逻辑,而不是像存储持久性插件中那样进行简单的 JSON(反)序列化。
依赖注入并不是类所独有的,并且可以以合适的方式执行,例如对于 Pinia 商店:
在许多情况下,最好处理 Pinia 存储可组合项而不是存储实例,因为这可以解决循环依赖关系,如果过早调用可组合项,循环依赖关系可能会成为问题。类也可能出现同样的问题,并且需要使用 DI 容器而不是直接使用类实例。
继承没有问题,因为可重用代码可以用 FP 而不是 OOP 来处理。 Vue 没有明确推广它,但使前者更惯用且使用起来更舒适。
TL;DR:坚持使用普通对象和 FP,因为这是 Vue 反应性设计的主要情况。