Pinia 全新一代状态管理工具

154 阅读4分钟

在2022年2月7号Vue3成为默认版本。Pinia的诞生严重威胁了Vuex 的地位,相信每个人都会抛弃 Vuex ,因为 Pinia 确实太方便和简单了。

记录2022年3月2号学习 Pinia

1.pinia 的优势

    1. 无论对 Vue2 还是 Vue 3 都能做到完美的兼容,新老项目都可以使用。
    2. 抛弃了 Mutations 的操作,只有 state、getters、actions。极大的简化了状态管理库的使用,让代码编写更加容易直视。
    3. 不需要嵌套模块,符合 Vue3 的 Composition api,让代码更加扁平化。
    4. 完美的 TypeScript 支持,Vuex 对 ts 的支持就不够完美。
    5. 实现更好的代码分割。
    6. 尤大大的强烈推荐,怎么能不算优势。

其实 pinia 的开发团队就是 vuex 的开发团队,pinia 取代 vuex 只是时间问题。

1.1 环境安装

我在使用的时候安装的版本为 2.0.11 ,为了保证使用方式相同,可以安装指定版本

npm install pinia -S
# or
yarn add pinia

# 安装指定版本
npm install pinia@2.0.11

2. 创建一个 Store

安装好 pinia 之后 直接在 /src/main.ts 即入口文件中引入 pinia。通过 createPinia() 方法得到 pinia 的实例和挂载到 Vue 跟实例上。

2.1 引用

/src/main.ts

import {createApp} from 'vue';
import App from './App.vue';
import router from './router/index';
import './utils/rem'
import {createPinia} from 'pinia'


const app = createApp(App);
// 使用路由
app.use(router);

// 挂在状态管理库 pinia
app.use(createPinia());

// 挂载
app.mount('#app');

2.2 创建状态管理库 store

/src/store/index.ts

/*
* 这里的代码只做三件事:
* 定义状态容器(仓库)
* 修改容器(仓库)中的 state
* 仓库中的 action 的使用
**/
import {defineStore} from 'pinia';

// 方法的第一个参数:相当于为容器起一个名字。注意:这里的名字必须唯一,不能重复
// 方法的第二个参数:可以简单理解为一个配置对象,里边是对容器仓库的配置说明。
export const useMainStore = defineStore('main', {
  // 用来存储全局的状态,这里定义就可以作为 SPA 里全局的状态了
  state: () => ({
    userinfo: {},
    count: 0
  }),
  // 用来监视或者说是计算状态变化的,有缓存功能
  getters: {},
  // 对 state 里数据变化的业务逻辑需求不同编写逻辑不同,就是修改 state 全局状态数据的
  actions: {}
})

2.3 在 Vue3 组件中读取 Store 数据

<template>
  <div class='home'>
    <img :src='mainStore.userinfo.face' alt='头像'>
    <div>{{ mainStore.count }}</div>
  </div>
</template>

<script lang='ts' setup>
import {useMainStore} from '@/store';
// 通过 useMainStore 得到 mainStore 实例,就可以在组件里调用 mainStore 里的 state 定义的状态数据了
const mainStore = useMainStore();
</script>

3. Pinia 改变状态数据和注意事项

这里说下状态数据修改,和状态数据解构时遇到的问题

3.1 实现状态数据的改变

跟组件中修改 userinfo 数据

/src/app.vue

<template>
  <router-view />
</template>
<script lang='ts' setup>
import {getUserinfo} from '@/api/global.js'
import {useMainStore} from '@/store';
const mainStore = useMainStore();

getUserinfo().then((res: any) => {
  // 直接改变数据,别的组件中的数据会同步更新
  mainStore.userinfo = res.obj;
})
</script>

3.2 解构 store 数据的坑

把 mainStore 中的数据直接进行解构

<template>
  <div class='home'>
    <img :src='userinfo.face' alt='头像'>
    <div>{{ count }}</div>
  </div>
</template>

<script lang='ts' setup>
import {useMainStore} from '@/store';
// 解构出来的 store 中的数据
const mainStore = useMainStore();
const {userinfo, count} = mainStore;
</script>

这里通过解构的数据,只有一次作用,不是响应式数据。也就是说当你改变数据状态时,解构的状态数据不会发生变化。

官方文档中有解决这个问题的方法,storeToRefs() 方法。

<template>
  <div class='home'>
    <img :src='userinfo.face' alt='头像'>
    <div>{{ count }}</div>
  </div>
</template>

<script lang='ts' setup>
import {useMainStore} from '@/store';
import {storeToRefs} from 'pinia';
// 解构出来的 store 中的数据
const mainStore = useMainStore();
// 对 mainStore 使用该方法,其实就是把解构出来的数据作了 ref 响应式处理,所以数据有了响应式的能力
const {userinfo, count} = storeToRefs(mainStore);
</script>

4. Pinia 修改状态数据的多种方式

上一节只是修改数据的一种方式,还有三种方式可以修改 store 中的数据。

4.1 使用 $patch 修改多条数据

/src/app.vue

<template>
  <router-view />
</template>
<script lang='ts' setup>
import {getUserinfo} from '@/api/global.js'
import {useMainStore} from '@/store';
const mainStore = useMainStore();

getUserinfo().then((res: any) => {
  // 直接改变数据,别的组件中的数据会同步更新
  // 使用 $patch 同时修改多条数据
  mainStore.$patch({
    userinfo: res.obj,
    count: 100
  })
})
</script>

下面这种直接写两行代码也可以实现这样的效果。但是并不推荐下面这种写法。

mainStore.count = 100;
mainStore.userinfo = res.obj

因为 Pinia 官网已经说明 patch修改多条数据的方式是经过优化的,会加快修改的速度,对性能有一定的提升。所以修改多条数据的时候使用patch 修改多条数据的方式是经过优化的,会加快修改的速度,对性能有一定的提升。所以修改多条数据的时候使用 patch。

4.2 $patch 加函数的形式修改状态数据

上面的 $patch 方法,参数使用的是一个对象,还有一种传递函数的方式,适合复杂数据的修改(数组、对象)

  // 这个时候的 state 就是 store 仓库里的 state,所以我们可以直接在函数里改变任何状态数据的值
  mainStore.$patch(state => {
    state.userinfo = res.obj;
    state.count = 100;
  })

4.3 在 actions 中写好逻辑,再调用 actions

如果修改数据的过程非常复杂,可以先在 store 中定义好 actions 中的函数,然后在组件里再掉用函数。

/src/store/index.ts 中的 actions 中添加方法

actions: {    
  // 这里不能使用箭头函数,因为箭头函数绑定的是外部的 this,这里注意下
  changeState(){
    this.count = 100;
    this.userinfo = {a: 111}
  }
}

/src/app.vue 在 app 组件中调用

<template>
  <router-view />
</template>
<script lang='ts' setup>
import {getUserinfo} from '@/api/global.js'
import {useMainStore} from '@/store';
const mainStore = useMainStore();

getUserinfo().then((res: any) => {
  // 调用 store 里 action 中的方法修改数据
  mainStore.changeState();
})
</script>

5. Pinia 中 Getters 使用

Pinia 中的 Getter 和 Vue 中的计算属性几乎一样,就是在获取 State 的值时作一些处理。比如我们有这样一个需求,就是在 state 里有一个状态数据是电话号码,想输出的时候中间四位数为 **** ,这时用 Getter 就可以完美解决。

5.1 使用

/src/store/index.ts 中 state 数据

state: () => ({
  phone: 18766668888,
  count: 1
})

getters 中编写方法,通过正则表达式隐藏手机号中间四位数

getters: {
  newPhone(state):string {
    return state.phone.toString().replace(/^(\d{3})\d{4}(\d{4})$/, '$1****$2')
  }
}

读取数据

<template>
  <div class='home'>
    <div>{{ count }}</div>
    <div>{{ phone }}</div>
    <div>{{ newPhone }}</div>
    <div>{{ mainStore.newPhone }}</div>
  </div>
</template>

<script lang='ts' setup>
import {useMainStore} from '@/store';
import {storeToRefs} from 'pinia';
const mainStore = useMainStore();
const {phone, newphone, count} = storeToRefs(mainStore);
</script>

5.2 Getters 的缓存属性

Getter 是由缓存特性的,5.1 中调用了两次 newPhone,其实 newPhone 中的代码只执行了一次,因为 getter 是由缓存的。当 newPhone 方法中的 phone 参数改变的时候,newPhone 方法也会相应调用一次,更新数据,清楚以前的数据缓存。

在第4节 actions 中可以直接使用 this 关键字操作,其实在 getters 中也一样可以使用 this 关键字。

getters: {
  newPhone():string {
    return this.phone.toString().replace(/^(\d{3})\d{4}(\d{4})$/, '$1****$2')
  }
}

6. Pinia 中 Store 的互相调用

在真实的项目中我们会创建多个 store 仓库,多个 store 仓库之间可能会涉及内部相互调用问题。

创建一个新的 store

/src/store/znStore.ts

import {defineStore} from 'pinia';

// 这里注意传入的第一个参数 id 需要是唯一的
export const znStore = defineStore('znMain', {
  state: () => ({
    like: ['刘亦菲', '佟丽娅']
  }),
  getters: {},
  actions: {}
})

在 /src/store/index.ts 中调用

// 引入 store
import {znStore} from './znStore';
actions: {    
  changeState(){
    this.count = 100;
    this.userinfo = {a: 111};
  },
  getLike(){
    // 这里是个方法,使用的时候要注意
    console.log(znStore().list);
  }
}

在 app.vue 中调用该方法,会打印 znStore 的数据出来

<template>
  <div class='home'>
  </div>
</template>

<script lang='ts' setup>
import {useMainStore} from '@/store';
import {storeToRefs} from 'pinia';
const mainStore = useMainStore();
// 执行 mainStore 中的方法
mainStore.getLike()
</script>

7. 安装 vue-devtools 调试

更加便利的开发。

以上学完 Pinia 的内容。

学习的来源地址来自于 jspang:jspang.com/detailed?id…