vue3 快速入门系列 —— 状态管理 pinia

  • vue3 快速入门系列 —— 状态管理 pinia已关闭评论
  • 22 次浏览
  • A+
所属分类:Web前端
摘要

其他章节请看:vue3 快速入门 系列vue3 状态管理这里选择 pinia。虽然 vuex4 已支持 Vue 3 的 Composition API,但是 vue3 官网推荐新的应用使用 pinia —— vue3 pinia


其他章节请看:

vue3 快速入门 系列

Pinia

vue3 状态管理这里选择 pinia

虽然 vuex4 已支持 Vue 3 的 Composition API,但是 vue3 官网推荐新的应用使用 pinia —— vue3 pinia

集中式状态管理

redux、mobx、vuex、pinia都是集中式状态管理工具。与之对应的就是分布式。

Pinia 符合直觉的 Vue.js 状态管理库 甚至让你忘记正在使用的是一个状态库 —— 官网

安装 pinia 环境

首先下载安装包:

PS hello_vue3> npm i pinia  added 2 packages, and audited 71 packages in 11s  10 packages are looking for funding   run `npm fund` for details  1 moderate severity vulnerability  To address all issues, run:         npm audit fix  Run `npm audit` for details.   
 "dependencies": {     "pinia": "^2.1.7",     "vue": "^3.4.15",     "vue-router": "^4.3.0"   }, 

在 main.ts 中依次:引入、创建和安装 pinia,在浏览器 vue 开发者工具中就能看到 pinia(一个菠萝图标)。

import {createApp} from 'vue' import App from './App.vue' import router from './router'  // 引入 import { createPinia } from 'pinia'  const app = createApp(App)  // 创建 const pinia = createPinia() app.use(router)  // 安装:就像安装 vue-router 一样使用 app.use(pinia) app.mount('#app') 

vue3 快速入门系列 —— 状态管理 pinia

有时这个菠萝没出现,可以关闭浏览器或重启服务。

Tip: 详细请看 pinia 安装官网

第一个示例

vuex 的核心概念有 State、Getters、Mutations、Actions和Modules。其中 State 是数据,我们不能直接修改数据。

pinia 比 vuex 更轻量,更易使用。比如拿到数据后就能直接改,符合直觉

请看示例:

pinia 的数据从项目目录上说,会放在 store 文件夹中。

通常我们会对状态进行分类,比如用户相关的数据放在 store/user.ts 中:

// src/store/user.ts import { defineStore } from 'pinia'  // 你可以任意命名 `defineStore()` 的返回值,但最好使用 store 的名字,同时以 `use` 开头且以 `Store` 结尾。 // (比如 `useUserStore`,`useCartStore`,`useProductStore`) // 第一个参数是你的应用中 Store 的唯一 ID。 Pinia 将用它来连接 store 和 devtools export const useUserStore = defineStore('user', {   // actions 里面放一个一个的方法,用于响应组件中的动作   actions: {     changeNameAndAge() {       // this Proxy(Object)       // 里面有 $state。在 vue2 中有 $watch、$on等以$开头的都是给程序员用的实例方法       console.log('this', this);              // 没必要通过 $state,直接访问即可       this.name += '~'       this.$state.age += 1     }   },   state: () => {     return {       name: 'peng',       age: 18,     }   }, }) 

通过 defineStore 定义一个 store,第一个参数是 store 的id,命名建议规范,例如使用文件名,导出方式这里选择分别导出,导出的名字使用 use+user+store。

state 是一个函数,返回的就是数据

actions 中是一个一个的方法,但不需要像 vuex 中需用 dispatch 触发。

接着在需要使用状态的地方使用。读取状态的方式有2种,修改状态的方式有3种:

// Home.vue <template>   <div>     <!-- 读取方式1 -->     <p>{{ userStore.name }}</p>     <!-- 读取方式2。方式1更方便 -->     <p>{{ userStore.$state.age }}</p>      <p><button @click="changeNameAndAge">修改方式1:change age and name</button></p>     <p><button @click="changeNameAndAge2">修改方式2:change age and name</button></p>     <p><button @click="changeNameAndAge3">修改方式3:change age and name</button></p>   </div> </template>  <script lang="ts" setup name="App"> // 写 '@/store/user.ts' vscode 报错:An import path can only end with a '.ts' extension when 'allowImportingTsExtensions' is enabled // 去掉 .ts 即可 import {useUserStore} from '@/store/user'  const userStore = useUserStore() // userStore: Proxy(Object) console.log('userStore: ', userStore);  function changeName() {   // 修改数据方式1:直接操作数据   // vue2中的vuex必须通过mutation更新数据,不能直接修改数据   userStore.name += '~' }  function changeNameAndAge() {   userStore.$state.name += '~'   userStore.$state.age += 1 }  function changeNameAndAge2() {   // $patch 用于批量修改   // patch 中文“碎片”,比如age 就是 pinia 中一个数据碎片   userStore.$patch({     name: userStore.name + '~',     age: userStore.age + 1   }) }  function changeNameAndAge3() {   // 调用 actions   userStore.changeNameAndAge() } </script> 

Tip: changeNameAndAge 会触发2次修改,而 changeNameAndAge2 使用 $patch 会进行批量修改,从开发者时间线中看到,只执行1次。如果很多数据同时修改,推荐使用 patch。

vue3 快速入门系列 —— 状态管理 pinia

优雅的读取数据

前面我们是这么读取 store 中数据:

<p>{{ userStore.name }}</p>  const userStore = useUserStore() 

如果需要读取的数据太多,在模板中就会出现很多 userStore,于是我们想到用 toRefs 解构解决。就像这样:

<p>优雅的读:{{ name }}</p>  import {toRefs} from 'vue' const userStore = useUserStore() const {name} = toRefs(userStore) 

但是 toRefs(userStore) 太重,通过console.log(toRefs(userStore)) 你会发现toRefs将 store 所有属性(包括方法)都转成 ref,其实我们只需要将数据转成 ref 即可。

pinia 也想到了这个问题,于是可以用 storeToRefs 替代。就像这样:

<p>优雅的读:{{ name }}</p>  import {storeToRefs} from 'pinia' const userStore = useUserStore()  const {name} = storeToRefs(userStore)  // storeToRefs(userStore): {name: ObjectRefImpl, age: ObjectRefImpl} console.log('storeToRefs(userStore): ', storeToRefs(userStore));  // toRefs(userStore): {$id: ObjectRefImpl, $onAction: ObjectRefImpl, $patch: ObjectRefImpl, $reset: ObjectRefImpl, $subscribe: ObjectRefImpl, …} console.log('toRefs(userStore): ', toRefs(userStore)); 

Getters

和 vuex 中 Getters 作用相同,用法类似。

这里用了两种方式定义 getters:

  state: () => {     return {       name: 'Peng',       age: 18,     }   },   getters: {     // 推荐使用箭头函数,参数会传入 state     bigName: (state) => state.name.toLocaleUpperCase(),      // 如果需要访问其他 getters 属性,可以通过非箭头函数,通过 this 访问     lowerName2(): string{       return this.bigName.toLocaleLowerCase()     }   }, 

数据取得的方式和 state 相同:

<p>优雅的读:{{ name }}</p> <p>bigName:{{ bigName }}</p> <p>lowerName2:{{ lowerName2 }}</p>  const userStore = useUserStore()  const {name, bigName, lowerName2} = storeToRefs(userStore) 

Tip:详细请看 pinia getters

订阅

类似于 Vuex 的 subscribe 方法,你可以通过 store 的 $subscribe() 方法侦听 state 及其变化

只要 userStore 中的数据变化了,函数就会被调用,我们通常关心第二个参数:

// 只要 userStore 数据变化,这个 userStore.$subscribe((mutation, state) => {   // {storeId: 'user', type: 'direct', events: {…}}   console.log('mutation: ', mutation);    // Proxy(Object) {name: 'Peng~', age: 19}   console.log('state: ', state);    // 每当状态发生变化时,将整个 state 持久化到本地存储。   localStorage.setItem('userStore', JSON.stringify(state)) }) 

我们可以将 state 存储到本地,这样就可以实现页面刷新,状态不丢失。

Tip: 细节请看 订阅 state

组合式写法

目前 actions state 写法属于声明式的:

import { defineStore } from 'pinia'  export const useUserStore = defineStore('user', {   actions: {     changeNameAndAge() {       this.name += '~'       this.$state.age += 1     }   },   state: () => {     return {       name: 'Peng',       age: 18,     }   },   getters: {     bigName: (state) => state.name.toLocaleUpperCase(),     lowerName2(): string{       return this.bigName.toLocaleLowerCase()     }   }, }) 

将其改成组合式。代码如下:

import { defineStore } from 'pinia'; import {ref, computed} from 'vue' export const useUserStore = defineStore('user', () => {   // 数据用 ref 或 reactive 定义   const name = ref('Peng')   const age = ref(18)      // getters 用计算属性   const bigName = computed(() => name.value.toLocaleUpperCase())   const lowerName2 = computed(() => bigName.value.toLocaleLowerCase())    // actions 用方法定义   function changeNameAndAge() {     name.value += '~';     age.value += 1;   }    // 最后必须暴露出去   return {     // vscode 中数据一个颜色、方法另一个颜色     name,     age,     bigName,     lowerName2,     changeNameAndAge,   }; }); 

Tip:组合式写法更灵活(请看 组合式 Store),层级少,但必须返回。具体如何选择自行决定。

扩展

ref 数据要不要 .value

const a = reactive({     x: 1,     y: 2,     z: ref(3) })  const b = ref(4) console.log(a.x) // ref 如果在里面,则不需要拆包 console.log(a.z) console.log(b.value) 

读取响应式对象中的 ref 不需要 .value

其他章节请看:

vue3 快速入门 系列