likes
comments
collection
share

Vue3.2: Pinia的用法之妙

作者站长头像
站长
· 阅读数 48

总结(倒叙写法)

根据源码的依据,以及文档的总结,Pinia是同时兼容 Vue2Vue3

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

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

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

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

  • 兼容 Vue2 和 Vue3

  • 抛弃传统的 Mutation ,只有 state, getteraction ,简化状态管理库

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

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

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

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

回忆Vuex

挪用官网的截图

Vue3.2: Pinia的用法之妙 从图中可以看出,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遇到同样的问题,而且其解决方案也是异曲同工 在状态改变时将其同步到浏览器的存储中,如 cookielocalStoragesessionStorage

可以搭配 pinia-persistedstate-plugin 插件来实现持久化,原理也是把数据存入localStorage中,只是插件会帮助自动存入与取出,不需要自己去操作localstorage计划:后续写一篇关于该插件使用方式的总结

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
      }
    }

  }

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

Vue3.2: Pinia的用法之妙

使用过程遇到的bug

使用store.$reset()重置数据

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

Vue3.2: Pinia的用法之妙

解决方法:

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)。
  • 无法序列化对象的原型链

附带查阅: Pinia中文文档

转载自:https://juejin.cn/post/7210681364470988861
评论
请登录