Vue3.0使用Pinia + 持久化

2,269 阅读3分钟

pinia和vuex的区别

  1. pinia他没有mutations,它只有state,getters,actions,其中actions支持同步和异步,可以使用它来修改state。
  2. pinia没有moudles配置,没一个独立的仓库都是defineStore出来的
  3. pinia语法上比vuex更容易理解和使用,灵活

Vuex和pinia的优缺点

pinia的优点:

  1. 完整的TypeScript支持;和Vuex相比添加TS更加容易
  2. 极其的轻巧(体积大约1kb)
  3. store的actions 被调用为常规函数,而不是像Vuex中使用dispath或者MapActions辅助函数。
  4. 支持多个Stroe

Pinia的缺点:

  1. 不支持时间旅行和编辑调试功能

Vuex的优点:

  1. 支持调试功能 如时间旅行和编辑
  2. 适用于大型、高度复杂的vue项目

Vuex的缺点:

  1. 从Vue3开始,getter的结果不会像计算属性那样缓存
  2. Vuex 4 又一些与类型安全相关的问题。

pinia的使用

pinia安装: npm install pinia

pinia持久化插件pinia-plugin-persist安装:npm i pinia-plugin-persist --save

main.ts初始化配置:


import { createApp } from 'vue'
import App from './App.vue'

//如果没有使用 pinia-plugin-persist持久化插件
import {createPinia} from 'pinia'
createApp(APP).use(store).use(router).use(createPinia()).mount('#app')

//如果使用持久化
import piniaPersist from 'pinia-plugin-persist'
cosnt pinia = createPinia()
pinia.use(piniaPersist)
createApp(APP).use(store).use(router).use(pinia).mount('#app')

在store的目录下创建一个user.ts为例,我们先定义并导出一个名为user的模块

import { defineStore } from "pinia";
export const userStore = defineStore("user", {
  state: () => {
    return {
      count: 1,
      age: 11,
    };
  },
  //整个仓库持久化存储
  persist: {
    enabled: true,
    //指定字段存储,并且指定存储方式:
    strategies: [
      { storage: sessionStorage, paths: ['count', 'age'] }, // age 和 count字段用sessionStorage存储
      { storage: localStorage, paths: ['accessToken'] }, // accessToken字段用 localstorage存储
    ],
  },
});

defineStore 接收两个参数

第一个参数就是模块的名称,必须是唯一的,多个模块不能重名,Pinia 会把所有的模块都挂载到根容器上
第二个参数是一个对象,里面的选项和 Vuex 差不多

  • 其中 state 用来存储全局状态,它必须是箭头函数,为了在服务端渲染的时候避免交叉请求导致的数据状态污染所以只能是函数,而必须用箭头函数则为了更好的 TS 类型推导
  • getters 就是用来封装计算属性,它有缓存的功能
  • actions 就是用来封装业务逻辑,修改 state

访问 state

比如我们要在页面中访问 state 里的属性 count

由于 defineStore 会返回一个函数,所以要先调用拿到数据对象,然后就可以在模板中直接使用了

如下这样通过 store.xxx 使用,是具备响应式的

<template> 
    <div>{{ store.count }}</div> 
</template> 
<script lang="ts" setup> 
    import { userStore } from '../store' 
    const store = userStore() 
    // 解构 
    // const { count } = userStore() 
</script>

比如像注释中的解构出来使用,也可以用,只是这样拿到的数据不是响应式的,如果要解构还保持响应式就要用到一个方法 storeToRefs(),示例如下

<template> 
    <div>{{ count }}</div> 
</template> 
<script lang="ts" setup> 
import { storeToRefs } from 'pinia' 
import { userStore } from '../store' 
const { count } = storeToRefs(userStore()) </script>

getters

这个和 Vuex 的 getters 一样,也有缓存功能。如下在页面中多次使用,第一次会调用 getters,数据没有改变的情况下之后会读取缓存

<template>
    <div>{{ myCount }}</div>
    <div>{{ myCount }}</div>
    <div>{{ myCount }}</div>
</template>

注意两种方法的区别,写在注释里了

getters: {
    // 方法一,接收一个可选参数 state
    myCount(state){
        console.log('调用了') // 页面中使用了三次,这里只会执行一次,然后缓存起来了
        return state.count + 1
    },
    // 方法二,不传参数,使用 this
    // 但是必须指定函数返回值的类型,否则类型推导不出来
    myCount(): number{
        return this.count + 1
    }
}

更新和 actions

<template>
    <div>{{ user_store.count }}</div>
    <button @click="handleClick">按钮</button>
</template>
<script lang="ts" setup>
import { userStore } from '../store'
const user_store = userStore()
const handleClick = () => {
    // 方法一
    user_store.count++
    
    // 方法二,需要修改多个数据,建议用 $patch 批量更新,传入一个对象
    user_store.$patch({
        count: user_store.count1++,
        // arr: user_store.arr.push(1) // 错误
        arr: [ ...user_store.arr, 1 ] // 可以,但是还得把整个数组都拿出来解构,就没必要
    })
    
    // 使用 $patch 性能更优,因为多个数据更新只会更新一次视图
    
    // 方法三,还是$patch,传入函数,第一个参数就是 state
    user_store.$patch( state => {
        state.count++
        state.arr.push(1)
    })
}
</script>

第四种方法就是当逻辑比较多或者请求的时候,我们就可以封装到示例中 store/user.ts 里的 actions 里

可以传参数,也可以通过 this.xx 可以直接获取到 state 里的数据,需要注意的是不能用箭头函数定义 actions,不然就会绑定外部的 this 了

actions: {
    changeState(num: number){ // 不能用箭头函数
        this.count += num
    }
}

调用

const handleClick = () => {
    user_store.changeState(1)
}