Vue3.2: Pinia的用法之妙

您所在的位置:网站首页 dispose的搭配 Vue3.2: Pinia的用法之妙

Vue3.2: Pinia的用法之妙

2023-06-09 20:26| 来源: 网络整理| 查看: 265

本文正在参加「金石计划」

总结(倒叙写法)

这里先声明一下,下面的总结都是有具体实例和分析的,有需要的JYM请耐心看完😂

根据源码,以及文档的总结,Pinia是同时兼容 Vue2 和 Vue3

那么我们为什么要使用它呢,或者说什么时候才需要使用它? 相信这是每一个刚接触到这个Pinia名词的时候,都带着的疑问

官网给出的解释:Pinia 是 Vue 的存储库,它允许您跨组件/页面共享状态, 哈哈,概念很明显,这不正是我们在 Vue2 所使用的 Vuex 的功用一致吗。那就不做过多的解读了。

当然升级版本,就像 Vue2 升级到 Vue3 所带来的诸多好处一样。Vuex 换成 Pinia ,总有其所备受推崇的理由

这里就说下我所知道的关于 Pinia 的优点:

兼容 Vue2 和 Vue3

抛弃传统的 Mutation ,只有 state, getter 和 action ,简化状态管理库

不需要嵌套 modules , 使用 Composition api (Pinia 最初是在 2019 年 11 月左右重新设计使用 Composition API)

TypeScript支持 (不需要自个添加TS包装器)

扁平化设计,没有 命名空间模块。鉴于 Store 的扁平架构,“命名空间” Store 是其定义方式所固有的,也可以说是所有 Store 都是命名空间的

待JYM的帮补充......

回忆Vuex

挪用官网的截图

image.png 从图中可以看出,Action、Mutations、State共同组成了Vuex全局单例模式管理系统 简略说下Action、Mutations、State的作用

State负责存储整个应用的状态数据 Mutations的中文意思是“变化”,利用它可以更改状态,本质就是用来处理数据的函数 Actions也可以用于改变状态,不过是通过触发mutation实现的,重要的是可以包含异步操作

他们的组合形成了一个store对象,可以把它看作是一个容器,如下面代码的样子

const mutations = {...}; const actions = {...}; const state = {...}; Vuex.Store({ state, actions, mutation });

我们都知道,Vuex的状态,在刷新页面后会清理内存,数据会丢失,后面我们通过了两种方案,解决了这个问题,分别是:

一、localStorage存储、监听,回显,刷新页面的时候重新赋值给vuex,类似于初始化的作用 二、利用第三方库进行持久化存储, vuex-persistedstate 疑问Vue3后Pinia数据丢失?

那么我想问,使用Pinia,当页面刷新的时候,是否也会丢失数据呢?

看到这里的JYM可能会想,既然提出了这个问题,应该是跟vuex是有反差的,也就是觉得不会丢失数据/

其实,pinia 的状态与vuex一样把数据存在内存中,也就是,会和Vuex遇到同样的问题,而且其解决方案也是异曲同工 在状态改变时将其同步到浏览器的存储中,如 cookie、localStorage、sessionStorage。

可以搭配 pinia-persistedstate-plugin 插件来实现持久化,原理也是把数据存入localStorage或其他存储区域中,只是插件会帮助自动存入与取出,不需要自己去操作localstorage。 如图:

QQ20230316-084110-HD.gif

当然这个插件的使用也是得益于Pinia开发式API 由于是底层 API,Pania Store可以完全扩展。 以下是我们可以执行的操作列表:

向 Store 添加新属性 定义 Store 时添加新选项 为 Store 添加新方法 包装现有方法 更改甚至取消操作 实现本地存储等副作用 仅适用于特定 Store

使用 pinia.use() 将插件添加到 pinia 实例中,比如刚才说到的pinia-persistedstate-plugin

Pinia的改进

从总结中,说到Pinia摒弃了mutation

为什么Pinia不需要mutation了?

我从开始接触Vuex的时候,就觉得mutation特别繁琐,当时就在想,为啥一定要将同步和异步的方法分开呢?

然后我带着疑问查阅资料和翻看源码,大致是这样子:

vuex中的Mutation真的没必要吗?

在 vuex 里面 actions 只是一个架构性的概念,这个函数想实现什么同步或者异步看你自己的需求,并不作限制,但是若想改变state的状态,需要通过mutation去触发。vuex 真正限制的只有 mutation 必须是同步的这一点。其实是为了能用 devtools 追踪状态变化,

同步的意义在于每一个 mutation 执行完成后都可以对应到一个新的状态(和 reducer 一样),这样 devtools 就可以打个 snapshot 存下来,然后就可以随便 time-travel 了。如果你开着 devtool 调用一个异步的 action,你可以清楚地看到它所调用的 mutation 是何时被记录下来的,并且可以立刻查看它们对应的状态。

这样一分析,在Vuex中使用mutation并不是完全没有必要的。

到了Pinia, 将同步异步,统一合并到了action里面,并区分判断同步异步,看下面的源码

const partialStore = { _p: pinia, // _s: scope, $id, $onAction: addSubscription.bind(null, actionSubscriptions), $patch, $reset, $subscribe(callback, options = {}) { const removeSubscription = addSubscription(subscriptions, callback, options.detached, () => stopWatcher()); const stopWatcher = scope.run(() => vueDemi.watch(() => pinia.state.value[$id], (state) => { if (options.flush === 'sync' ? isSyncListening : isListening) { callback({ storeId: $id, type: exports.MutationType.direct, events: debuggerEvents, }, state); } }, assign({}, $subscribeOptions, options))); return removeSubscription; }, $dispose, }; Store的定义方式 非composition API定义方式 export const useCounterVue2Store = defineStore('counterVue2', { state: () => ({ count: 0 }), getters: { double: (state) => state.count * 2, }, actions: { increment() { this.count++ }, incrementAsync() { } }, persist: { key: 'counterVue2', storage: localStorage } })

保留了state、action以前的部分方式

composition API定义方式 export const useCounterStore = defineStore('counter', () => { const count = ref(0) const doubleCount = computed(() => count.value * 2) function increment() { count.value++ } return { count, doubleCount, increment } }, { persist: { key: 'userCounter', storage: localStorage } } ) Store中使用getter

也许这里有人会有困惑,getter每次调用一次,是不是就要计算一次表达公示,比如下面的代码。

其实不然,跟computed一样,当里面依赖的变量不发生改变的时候,是不会重新计算的,它们有数据缓存机制,可以自行测试一下,将getter写成一个方法,然后从中做个断点或者打印值就知道了。

getters: { double: (state) => state.count * 2, } getter中调用其它getter

可以直接在getter方法中调用this,this指向的便是store实例, 这里要注意,就不能使用箭头函数了,关于this指向的问题

export const useCounterVue2Store = defineStore('counterVue2', { state: () => ({ count: 0 }), getters: { double: (state) => state.count * 2, doubleTwo(): number { return this.double + 22 } }, actions: { increment() { this.count++ }, incrementAsync() { } } }) getter中调用其它store的getter getters: { double: (state) => state.count * 2, doubleTwo(): number { return this.double + 22 }, // 调用其他store的getter doubleAddOtherStoreGetter: (state) => { const otherCounter = useCounterStore() return otherCounter.count + state.count + 444 } } getter传参的实现

按照官网的说法,Getters 只是幕后的 computed 属性,因此无法向它们传递任何参数。

但是,可以从 getter 返回一个函数以接受任何参数:

getters: { double: (state) => state.count * 2, doubleTwo(): number { return this.double + 22 }, // 调用其他store的getter doubleAddOtherStoreGetter: (state) => { const otherCounter = useCounterStore() return otherCounter.count + state.count + 444 }, getFunctionVoid: (state) => { const otherCounter = useCounterStore() return (val: number) => { return 1100 + otherCounter.count } } }

注意: 如果这样使用,getter 不会缓存,它只会当作一个普通函数使用。一般不推荐这种用法,因为在组件中定义一个函数,可以实现同样的功能,如图

getFunctionVoid: (state) => { const otherCounter = useCounterStore() console.log('getFunctionVoid') let i = 0; return (val: number) => { console.log(i++) return 1100 + otherCounter.count } }

image.png

使用了三次,就连续打印了三次

Store中actions

Actions 相当于组件中的 methods 与 getters 一样,操作可以通过 this 访问 whole store instance 并提供完整类型(和自动完成✨)支持。 与它们不同,actions 可以是异步的,您可以在其中await 任何 API 调用甚至其他操作

订阅 Actions

具体的使用方法可以看官网的介绍,订阅Actions

一般应用场景需要到的,是消息通知相关

Store的一些函数 Store的$patch

$patch(第一个参数支持一个对象/函数)

如下:

对象

store.$patch({ count: counter.count + 22, });

函数

store.$patch( (state) => { state.items.push({ name: 'shoes', quantity: 1 }) state.hasChanged = true }); Store的$subscribe

可以通过 store 的 $subscribe() 方法查看状态及其变化,类似于 Vuex 的 subscribe 方法。 与常规的 watch() 相比,使用 $subscribe() 的优点是 subscriptions 只会在 patches 之后触发一次,意思就是每次执行$patch的时候,会触发一次。

此订阅默认组建销毁后被自动删除,若想保留可{ detached: true } 作为第二个参数传递$subscribe

QQ20230315-152825-HD.gif

使用过程遇到的bug 使用store.$reset()重置数据

Uncaught Error: 🍍: Store "counter" is built using the setup syntax and does not implement $reset().

image.png

解决方法: import { createPinia } from 'pinia'; import { createPersistedState } from 'pinia-plugin-persistedstate'; const store = createPinia(); store.use(({ store }) => { const initialState = JSON.parse(JSON.stringify(store.$state)); store.$reset = () => { store.$state = JSON.parse(JSON.stringify(initialState)); } });

当然这种使用JSON.Stringify的解决方案会存在一定的问题

使用JSON.Stringify 转换的数据中,如果包含 function,undefined,Symbol,这几种类型,不可枚举属性, JSON.Stringify序列化后,这个键值对会消失。 转换的数据中包含 NaN,Infinity 值(含-Infinity),JSON序列化后的结果会是null。 转换的数据中包含Date对象,JSON.Stringify序列化之后,会变成字符串。 转换的数据包含RegExp 引用类型序列化之后会变成空对象。 无法序列化不可枚举属性。 无法序列化对象的循环引用,(例如: obj[key] = obj)。 无法序列化对象的原型链 使用store更新state数据其他子页面没有同步更新

我在网上搜寻的解说是如下:

image.png 但实际在使用中,我直接访问存储对象也是可以同步的,上面的解答无疑是无中生有

后面经过检查代码发现在

image.png 访问state的count的时候,是把this.count写成了state.count才导致的无法更新

注意:computed(() => userDataStore.getProjects);这种使用computed并不是为了对单一属性的监听,而是用于计算属性用法才是恰当的

附带查阅: Pinia中文文档

本文正在参加「金石计划」



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3