Vue状态管理工具:Pinia

基本概念

Pinia 是一个专为 Vue.js 设计的状态管理库,特别是针对 Vue 3 进行了优化,完美支持Vue3的Composition api 以及TypesCcript语法。它提供了一种更加简单、直观且可扩展的方式来组织和访问应用程序的状态。目前Pinia已经取代Vuex成为vue官方文档生态的一部分。

与vuex相比,Pinia主要有以下优点:

  1. Pinia 的 API 采用Vue 3的Composition API风格,设计更加简洁明了,易于理解和使用,状态管理更加直观。
  2. Pinia构建得更加轻量级,其体积比Vuex小,这有助于减少应用程序的加载时间和提高性能。
  3. Pinia 支持 TypeScript,提供了类型安全的 API ,有助于在开发过程中捕获错误和进行静态类型检查。
  4. Pinia支持创建多个store全局实例,每个store都可以视为一个独立的状态管理模块。
  5. Pinia 不需要嵌套模块,符合Vue3的Composition api ,让代码更加扁平化。

官方文档:Pinia | The intuitive store for Vue.js (vuejs.org)


使用步骤

1.安装Pinia

npm install pinia

2.main.js中引入pinia

import { createApp } from "vue";
import { createPinia } from "pinia"; //引入pinia
import App from "./App.vue";

const pinia = createPinia(); //创建pinia实例
const app = createApp(App);
app.use(pinia);  //挂载实例
app.mount("#app");

3.创建仓库Option Store:store.js文件(后面会介绍Setup Store)

import { defineStore } from "pinia";

export const useMainStore = defineStore("main", {
  state: () => ({}),
  getters: {},
  actions: {},
});
  1. defineStore的第一个参数:是容器的唯一标识,不能重复
  2. defineStore的第二个参数:是对容器仓库的配置说明(对象)
  3. state 属性: 存储全局状态数据data
  4. getters属性:计算属性,有缓存机制
  5. actions属性: 对state里数据变化的业务逻辑,简单说就是修改state全局状态数据

4.在组件中使用:需要引入和实例化

虽然我们前面定义了一个 store,但在我们使用 <script setup> 调用 useStore()(或者使用 setup() 函数,像所有的组件那样) 之前,store 实例是不会被创建的:

<script setup>
import { useMainStore } from "../stores/index";//引入
const store= useMainStore();//获取实例
// 可以在组件中的任意位置访问 `store` 变量 ✨
console.log(store);
</script>

核心概念

1. state

1).定义state

在大多数情况下,state 都是 store 的核心。在 Pinia 中,state 被定义为一个返回初始状态的函数。这使得 Pinia 可以同时支持服务端和客户端。

//index.js
export const useMainStore = defineStore("main", {
  state: () => ({
    name: "Jack",
    age: 18,
  })
});

2).访问state数据

//组件script中
import { useMainStore } from "../stores/index";//引入

const store = useMainStore();
//方式一:
console.log(store.name,store.age);//输出 Jack 18

//方式二:
const { name, age} = store //解构构赋值
console.log(name,age);//输出Jack 18

//template模板中
{{store.name + store.age}}或解构的{{name + age}}

注意:虽然在数据较多的时候,方式二解构赋值相较于store.[‘变量名’]的方式更加简洁明了。但使用这种解构赋值法会丢失state数据的响应性。因为store 是一个用reactive 包裹的对象

解决方式是:使用官方提供的storeToRefs()方法。它将为每一个响应式属性创建引用。

import { storeToRefs } from "pinia"//引入
const { name, age} = storeToRefs(store)//使用

补充:其实在Vuex中,直接解构数据也是不可以的。


3).修改state数据

方式一:直接修改,通过对store.[属性],对访问的属性重新赋值

function changeState(){
    store.name = '小明'
    store.age = '666'
}

方式二:$patch方法修改,它允许你用一个 state 的补丁对象在同一时间更改多个属性(修改多条数据时,性能更高)

function changeState(){
  store.$patch({
    name: "王小明",
    age: 666,
  });
}

不过,用这种语法的话,有些变更真的很难实现或者很耗时:任何集合的修改(例如,向数组中添加、移除一个元素或是做 splice 操作)都需要创建一个新的集合。因此,$patch 方法也接受一个函数来组合这种难以用补丁对象实现的变更。

//index.js
export const useMainStore = defineStore("main", {
  state: () => ({
    items: [],
    hasChanged: false,
  }),
});

//组件内 修改
function changeState(){
  store.$patch((state) => {
    state.items.push({ name: 'shoes', quantity: 1 })
    state.hasChanged = true
  })
}

4).重置state

整个state的属性值都会变成定义时的初始值

function resetState(){
  store.$reset()
}

2. getter

1).定义getter

Getter 完全等同于 store 的 state 的计算值推荐使用箭头函数,并且它将接收 state 作为第一个参数:

export const useMainStore = defineStore("main", {
  state: () => ({
    name: "Jack",
    age: 18,
  }),
  getters: {
    doubleAge: (state) => state.age * 2,
    //在getter中 使用另一个getter  this指向当前存储库
    addOneAge() {
      return this.doubleAge + 1;
    },
    //或使用箭头函数但要将getters作为参数传入。(this指向问题)
    addTowAge: (getters) => getters.doubleAge + 2,
  }
});

 2).使用getter

//组件script中
import { useMainStore } from "../stores/index";
const store = useMainStore();

//方式一:
console.log(store.doubleAge);

//方式二:解构赋值
import { storeToRefs } from "pinia"
const { doubleAge, addOneAge , addTowAge} = storeToRefs(store)
//.value访问计算属性的值
console.log(doubleAge.value,addOneAge.value, addTowAge.value);

//temlate模板中
<p>{{store.doubleAge}}</p>
<p>{{store.addOneAge}}</p>
<p>{{store.addTowAge}}</p>

getter相当于state的计算属性,具有缓存机制。它的值会基于state依赖被缓存。仅会在state依赖被更新时才重新计算。只要依赖的state数据不变,无论访问多少次计算结果,它都会立即返回先前的计算结果,而不用重复执行 getter 函数。


 3. action

1).定义action

Action 相当于组件中的 method。它们是定义业务逻辑的完美选择。类似 getter,action 也可通过 this 访问整个 store 实例。不同的是,action 可以是异步的,你可以在它里面 await 调用任何 API,以及其他 action!

export const useCounterStore = defineStore("main", {
  state: () => ({
    count: 0,
  }),
  actions: {
    increment() {
      this.count++;
    },
    //模拟异步请求函数
    randomizeCounter() {//1 s后但会一个值未随机数字的promise对象
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          let number = Math.round(100 * Math.random());
          resolve(number);
        }, 1000);
      });
    },
    //异步操作
    async changeCount() {
      const res = await this.randomizeCounter();//解析promise
      this.count = res;
      //调用其他action
      this.increment();
    },
  },
});

 2).使用action

//script中
const store = useCounterStore();

//方式一:
// 将 action 作为 store 的方法进行调用
store.changeCount()

//方式二:
//解构赋值,直接使用
const { changeCount , increment }= store
changeCount()
increment()

//template模板中
<button @click="store.changeCount()">changeCount</button>

 注意:作为 action 的 方法可以直接解构


其他补充

1. Setup Store

这是另一种定义 store 的语法。与 Vue 组合式 API 的 setup 函数相似,我们可以传入一个函数,该函数定义了一些响应式属性和方法,并且返回一个带有我们想暴露出去的属性和方法的对象。

import { defineStore } from "pinia";
import { ref } from "vue";

export const useCounterStore = defineStore("counter", () => {
  const count = ref(0);
  const doubleCount = computed(() => count.value * 2);

  function increment() {
    count.value++;
  }
  return { count, doubleCount, increment };
});

在 Setup Store 中:ref() 就是 state 属性,computed() 就是 getters,function() 就是 actions

注意,要让 pinia 正确识别 state,你必须在 setup store 中返回 state 的所有属性。这意味着,你不能在 store 中使用私有属性。不完整返回会影响 SSR ,开发工具和其他插件的正常运行。

相比于上面介绍的Option Store,Setup store拥有更高的灵活性,因为你可以在store内创建侦听器,并自由地使用任何组合式函数。

Setup store 也可以依赖于全局提供的属性,比如路由。任何应用层面提供的属性都可以在 store 中使用 inject() 访问,就像在组件中一样:

import { inject } from 'vue'
import { useRoute } from 'vue-router'
import { defineStore } from 'pinia'

export const useSearchFilters = defineStore('search-filters', () => {
  const route = useRoute()
  // 这里假定 `app.provide('appProvided', 'value')` 已经调用过
  const appProvided = inject('appProvided')
  // ...
  return {
    // ...
  }
})

总之,两种语法都有各自的优势和劣势,Option Store更容易使用,而Setup Store更灵活和强大。


2. pinia持久化存储

pinia 和 vuex 一样,数据是短时的,只要一刷新页面,数据就会恢复成初始状态,为了避免这个问题,可以对其采用持久化保存方法。

持久化保存的原理是在 pinia 中数据更新时,同步保存到 localStorage 或 sessionStorage 中,刷新后从本地存储中读取数据。

1).安装插件

官方文档:开始使用 |Pinia 插件持续存在

npm i pinia-plugin-persistedstate

2).引入使用插件:main.js文件

import { createApp } from "vue";
import App from "./App.vue";
import { createPinia } from "pinia"; //引入pinia
import persist from 'pinia-plugin-persistedstate'//引入插件

const pinia = createPinia(); //创建pinia实例
pinia.use(persist)//使用插件

const app = createApp(App);
app.use(pinia); //挂载pinia到vue实例
app.mount("#app");

3).使用插件

方式一:默认保存,将当前模块中的所有数据都进行持久化存储,保存在localStorage 或sessionStorage 中,刷新页面不需要手动读取数据,插件会自动读取。

//setup store
defineStore(
  "counter",
  () => {
    return { };
  },
  { persist: true }// true 表示开启持久化保存
);

//option store
defineStore("main", {
  state: () => ({}),
  actions: {},
  persist: true,
});

方式二:传递一个对象给 Store 的 persist 属性来配置持久化。

persist: {
    key: "counterStore", //存储名称
    storage: sessionStorage, // 存储方式
    paths: ["count"], 
    //paths指定 state 中哪些数据需要被持久化。
    //[] 表示不持久化任何状态,undefined 或 null 表示持久化整个 state
  },

效果如图:

64ffbf5388444e4b81e2e0553075f4d1.png


3. 封装pinia

 封装的目的是pinia独立维护,减少main.js文件的冗余度。同时因为Pinia支持创建多个store全局实例,封装pinia可更加方便统一管理这些仓库。举例说明:

1).创建仓库userStore:user.js文件

import { defineStore } from "pinia";

export const useUserStore = defineStore("user", {
  state: () => ({
    name: "Jack",
    age: 18,
  }),
  getters: {
    doubleAge: (state) => state.age * 2,
  },
  actions: {
    increment() {
      this.age++;
    },
  },
  persist: true,
});

2).创建仓库counterStore:counter.js文件

import { defineStore } from "pinia";
import { ref, computed } from "vue";

export const useCounterStore = defineStore(
  "counter",
  () => {
    //数据state
    const count = ref(1);
    //getter
    const doubleCount = computed(() => count.value * 2);
    //action
    function increment() {
      this.count++;
    }
    return {
      count,
      doubleCount,
      increment,
    };
  },
  {
    persist: true,
  }
);

3).封装pinia

import { createPinia } from "pinia"; //引入pinia
import persist from "pinia-plugin-persistedstate"; //引入持久化插件

const pinia = createPinia(); //创建pinia实例
pinia.use(persist); //使用插件

export default pinia; //导出pinia用于main.js注册

//统一导出,可以控制各个仓库是否可用
// import { useUserStore } from '@/stores/modules/user.js'
// export { useUserStore }
// import { useCounterStore } from '@/stores/modules/counter.js'
// export { useCounterStore }

// 简写
export * from "./modules/user";
export * from "./modules/counter";

4).在mian.js中注册pinia

import { createApp } from "vue";
import App from "./App.vue";
import pinia from "./stores/index";

const app = createApp(App);

app.use(pinia); //挂载pinia到vue实例
app.mount("#app");

文件结构: 

03cf1ac7a31f4a7785152d90a7a42bd3.png

至此pinia封装完成,由于pinia具有较强的可扩展性。如果需要添加其他插件扩展便可一并放入index.js文件独立维护。参考官方文档:插件 | Pinia (vuejs.org)

 5).在组件中使用:只需要从index.js文件中导入,减少代码复杂度

import { useCounterStore } from '../stores/index'
const counterStore = useCounterStore()

 若有错误或描述不当的地方,烦请评论或私信指正,万分感谢 😃

  • 16
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值