乐趣区

关于vue.js:一文搞懂pinia状态管理

Vue3 曾经推出很长时间了,它周边的生态也是越来越欠缺了。之前咱们应用 Vue2 的时候,Vuex 能够说是必备的,它作为一个状态管理工具,给咱们带来了极大的不便。Vue3 推出后,尽管绝对于 Vue2 很多货色都变了,然而外围的货色还是没有变的,比如说状态治理、路由等等。再 Vue3 种,尤大神举荐咱们应用 pinia 来实现状态治理,他也说 pinia 就是 Vuex 的新版本。那么 pinia 到底是何方神圣,本篇文章带大家一起学透它!

1.pinia 是什么?

如果你学过 Vue2,那么你肯定应用过 Vuex。咱们都晓得 Vuex 在 Vue2 中次要充当状态治理的角色,所谓状态治理,简略来说就是一个存储数据的中央,寄存在 Vuex 中的数据在各个组件中都能拜访到,它是 Vue 生态中重要的组成部分。

既然 Vuex 那么重要,那么在 Vue3 中岂能抛弃!

在 Vue3 中,能够应用传统的 Vuex 来实现状态治理,也能够应用最新的 pinia 来实现状态治理,咱们来看看官网如何解释 pinia 的。
官网解释:Pinia 是 Vue 的存储库,它容许您跨组件 / 页面共享状态。

从下面官网的解释不难看出,pinia 和 Vuex 的作用是一样的,它也充当的是一个存储数据的作用,存储在 pinia 的数据容许咱们在各个组件中应用。实际上,pinia 就是 Vuex 的升级版,官网也说过,为了尊重原作者,所以取名 pinia,而没有取名 Vuex,所以大家能够间接将 pinia 比作为 Vue3 的 Vuex。

2. 为什么要应用 pinia?

很多小伙伴心田是抗拒学习新货色的,比方咱们这里所说的 pinia,很多小伙伴可能就会抛出一系列的疑难:为什么要学习 pinia?pinia 有什么长处吗?既然 Vue3 还能应用 Vuex 为什么我还要学它?……

针对下面一系列的问题,我置信很多刚开始学习 pinia 的小伙伴都会有,包含我本人当初也有这个疑难。当然,这些问题其实都有答案,咱们不可能平白无故的而去学习一样货色吧!必定它有本人的长处的,所以咱们这里先给出 pinia 的长处,大家心里先有个大略,当你纯熟应用它之后,在会过头来看这些长处,置信你能了解。

长处:
Vue2 和 Vue3 都反对,这让咱们同时应用 Vue2 和 Vue3 的小伙伴都能很快上手。

pinia 中只有 state、getter、action,摈弃了 Vuex 中的 Mutation,Vuex 中 mutation 始终都不太受小伙伴们的待见,pinia 间接摈弃它了,这无疑缩小了咱们工作量。

pinia 中 action 反对同步和异步,Vuex 不反对
良好的 Typescript 反对,毕竟咱们 Vue3 都举荐应用 TS 来编写,这个时候应用 pinia 就十分适合了

无需再创立各个模块嵌套了,Vuex 中如果数据过多,咱们通常分模块来进行治理,稍显麻烦,而 pinia 中每个 store 都是独立的,相互不影响。

体积十分小,只有 1KB 左右。

pinia 反对插件来扩大本身性能。

反对服务端渲染。

pinia 的长处还有十分多,下面列出的次要是它的一些次要长处,更多细节的中央还须要大家在应用的时候缓缓领会。

3. 筹备工作

想要学习 pinia,最好有 Vue3 的根底,明确组合式 API 是什么。如果你还不会 Vue3,倡议先去学习 Vue3。

本篇文章解说 pinia 时,全副基于 Vue3 来解说,至于 Vue2 中如何应用 pinia,小伙伴们能够自行去 pinia 官网学习,毕竟 Vue2 中应用 pinia 的还是多数。

我的项目搭建:咱们这里搭建一个最新的 Vue3 + TS + Vite 我的项目。执行命令:npm create vite@latest my-vite-app –template vue-ts 运行我的项目:npm install
npm run dev 删除 app.vue 中的其它无用代码,最终页面如下:

4.pinia 根底应用

4.1 装置 pinia

和 vue-router、vuex 等一样,咱们想要应用 pinia 都须要先装置它,装置它也比较简单。

装置命令:

yarn add pinia
# 或者应用 npm
npm install pinia

装置实现后咱们须要将 pinia 挂载到 Vue 利用中,也就是咱们须要创立一个根存储传递给应用程序,简略来说就是创立一个存储数据的数据桶,放到应用程序中去。

批改 main.js,引入 pinia 提供的 createPinia 办法,创立根存储。

代码如下:

// main.ts
​
​
import {createApp} from "vue";
import App from "./App.vue";
import {createPinia} from "pinia";
const pinia = createPinia();
​
​
const app = createApp(App);
app.use(pinia);
app.mount("#app");

4.2 创立 store
store 简略来说就是数据仓库的意思,咱们数据都放在 store 外面。当然你也能够把它了解为一个公共组件,只不过该公共组件只存放数据,这些数据咱们其它所有的组件都可能拜访且能够批改。

咱们须要应用 pinia 提供的 defineStore()办法来创立一个 store,该 store 用来寄存咱们须要全局应用的数据。

首先在我的项目 src 目录下新建 store 文件夹,用来寄存咱们创立的各种 store,而后在该目录下新建 user.ts 文件,次要用来寄存与 user 相干的 store。

代码如下:

/src/store/user.ts
​
​
import {defineStore} from 'pinia'
​
​
// 第一个参数是应用程序中 store 的惟一 id
export const useUsersStore = defineStore('users', {// 其它配置项})

创立 store 很简略,调用 pinia 中的 defineStore 函数即可,该函数接管两个参数:

name:一个字符串,必传项,该 store 的惟一 id。
options:一个对象,store 的配置项,比方配置 store 内的数据,批改数据的办法等等。
咱们能够定义任意数量的 store,因为咱们其实一个 store 就是一个函数,这也是 pinia 的益处之一,让咱们的代码扁平化了,这和 Vue3 的实现思维是一样的。

4.3 应用 store
后面咱们创立了一个 store,说白了就是创立了一个办法,那么咱们的目标必定是应用它,如果咱们要在 App.vue 外面应用它,该如何应用呢?

代码如下:

/src/App.vue
<script setup lang="ts">
import {useUsersStore} from "../src/store/user";
const store = useUsersStore();
console.log(store);
</script>

应用 store 很简略,间接引入咱们申明的 useUsersStore 办法即可,咱们能够先看一下执行该办法输入的是什么:

4.4 增加 state

咱们都晓得 store 是用来寄存公共数据的,那么数据具体存在在哪里呢?后面咱们利用 defineStore 函数创立了一个 store,该函数第二个参数是一个 options 配置项,咱们须要寄存的数据就放在 options 对象中的 state 属性内。假如咱们往 store 增加一些工作根本数据,批改 user.ts 代码。代码如下:

export const useUsersStore = defineStore("users", {state: () => {
    return {
      name: "猪课堂",
      age: 25,
      sex: "男",
    };
  },
});

上段代码中咱们给配置项增加了 state 属性,该属性就是用来存储数据的,咱们往 state 中增加了 3 条数据。须要留神的是,state 接管的是一个箭头函数返回的值,它不能间接接管一个对象。

4.5 操作 state

咱们往 store 存储数据的目标就是为了操作它,那么咱们接下来就尝试操作 state 中的数据。

4.5.1 读取 state 数据读取 state 数据很简略,后面咱们尝试过在 App.vue 中打印 store,那么咱们增加数据后再来看看打印后果:

这个时候咱们发现打印的后果外面多了几个属性,恰好就是咱们增加的数据,批改 App.vue,让这几个数据显示进去。代码如下:

<template>
  <img alt="Vue logo" src="./assets/logo.png" />
  <p> 姓名:{{name}}</p>
  <p> 年龄:{{age}}</p>
  <p> 性别:{{sex}}</p>
</template>
<script setup lang="ts">
import {ref} from "vue";
import {useUsersStore} from "../src/store/user";
const store = useUsersStore();
const name = ref<string>(store.name);
const age = ref<number>(store.age);
const sex = ref<string>(store.sex);
</script>

输入后果:

上段代码中咱们间接通过 store.age 等形式获取到了 store 存储的值,然而大家有没有发现,这样比拟繁琐,咱们其实能够用解构的形式来获取值,使得代码更简洁一点。

解构代码如下:

import {useUsersStore} from "../src/store/user";
const store = useUsersStore();
const {name, age, sex} = store;

上段代码实现的成果与一个一个获取的成果一样,不过代码简洁了很多。

4.5.2 多个组件应用 state
咱们应用 store 的最重要的目标就是为了组件之间共享数据,那么接下来咱们新建一个 child.vue 组件,在该组件外部也应用 state 数据。

child.vue 代码如下:

<template>
  <h1> 我是 child 组件 </h1>
  <p> 姓名:{{name}}</p>
  <p> 年龄:{{age}}</p>
  <p> 性别:{{sex}}</p>
</template>
<script setup lang="ts">
import {useUsersStore} from "../src/store/user";
const store = useUsersStore();
const {name, age, sex} = store;
</script>

child 组件和 app.vue 组件简直一样,就是很简略的应用了 store 中的数据。

4.5.3 批改 state 数据
如果咱们想要批改 store 中的数据,能够间接从新赋值即可,咱们在 App.vue 外面增加一个按钮,点击按钮批改 store 中的某一个数据。

代码如下:

<template>
  <img alt="Vue logo" src="./assets/logo.png" />
  <p> 姓名:{{name}}</p>
  <p> 年龄:{{age}}</p>
  <p> 性别:{{sex}}</p>
  <button @click="changeName"> 更改姓名 </button>
</template>
<script setup lang="ts">
import child from './child.vue';
import {useUsersStore} from "../src/store/user";
const store = useUsersStore();
const {name, age, sex} = store;
const changeName = () => {
  store.name = "张三";
  console.log(store);
};
</script>

上段代码新增了 changeName 办法,扭转了 store 中 name 的值,咱们点击按钮,看看最终成果:

咱们能够看到 store 中的 name 的确被批改了,然而页面上仿佛没有变动,这阐明咱们的应用的 name 不是响应式的。

很多小伙伴可能会说那能够用监听函数啊,监听 store 变动,刷新页面 …

其实,pinia 提供了办法给咱们,让咱们取得的 name 等属性变为响应式的,咱们从新批改代码。

app.vue 和 child.vue 代码批改如下:

import {storeToRefs} from 'pinia';
const store = useUsersStore();
const {name, age, sex} = storeToRefs(store);

咱们两个组件中获取 state 数据的形式都改为上段代码的模式,利用 pinia 的 storeToRefs 函数,将 sstate 中的数据变为了响应式的。

除此之外,咱们也给 child.vue 也加上更改 state 数据的办法。

child.vue 代码如下:

<template>
  <h1> 我是 child 组件 </h1>
  <p> 姓名:{{name}}</p>
  <p> 年龄:{{age}}</p>
  <p> 性别:{{sex}}</p>
  <button @click="changeName"> 更改姓名 </button>
</template>
<script setup lang="ts">
import {useUsersStore} from "../src/store/user";
import {storeToRefs} from 'pinia';
const store = useUsersStore();
const {name, age, sex} = storeToRefs(store);
const changeName = () => {store.name = "悟空";};
</script>

这个时候咱们再来尝试别离点击两个组件的按钮,实现成果如下:

当咱们 store 中数据发生变化时,页面也更新了!

4.5.4 重置 state

有时候咱们批改了 state 数据,想要将它还原,这个时候该怎么做呢?就比方用户填写了一部分表单,忽然想重置为最初始的状态。此时,咱们间接调用 store 的 $reset()办法即可,持续应用咱们的例子,增加一个重置按钮。代码如下:

<button @click="reset"> 重置 store</button>
// 重置 store
const reset = () => {store.$reset();
};

当咱们点击重置按钮时,store 中的数据会变为初始状态,页面也会更新。

4.5.5 批量更改 state 数据
后面咱们批改 state 的数据是都是一条一条批改的,比方 store.name=” 张三 ” 等等,如果咱们一次性须要批改很多条数据的话,有更加简便的办法,应用 store 的 $patch 办法,批改 app.vue 代码,增加一个批量更改数据的办法。

代码如下:

<button @click="patchStore"> 批量批改数据 </button>
// 批量批改数据
const patchStore = () => {
  store.$patch({
    name: "张三",
    age: 100,
    sex: "女",
  });
};

有教训的小伙伴可能发现了,咱们采纳这种批量更改的形式仿佛代价有一点大,如果咱们 state 中有些字段无需更改,然而依照上段代码的写法,咱们必须要将 state 中的所有字段例举出了。

为了解决该问题,pinia 提供的 $patch 办法还能够接管一个回调函数,它的用法有点像咱们的数组循环回调函数了。

示例代码如下:

store.$patch((state) => {state.items.push({ name: 'shoes', quantity: 1})
  state.hasChanged = true
})

上段代码中咱们即批量更改了 state 的数据,又没有将所有的 state 字段列举进去。

4.5.6 间接替换整个 state
pinia 提供了办法让咱们间接替换整个 state 对象,应用 store 的 $state 办法。

示例代码:

store.$state = {counter: 666, name: '张三'}

上段代码会将咱们提前申明的 state 替换为新的对象,可能这种场景用得比拟少,这里我就不开展阐明了。

4.6 getters 属性
getters 是 defineStore 参数配置项外面的另一个属性,后面咱们讲了 state 属性。getter 属性值是一个对象,该对象外面是各种各样的办法。大家能够把 getter 设想成 Vue 中的计算属性,它的作用就是返回一个新的后果,既然它和 Vue 中的计算属性相似,那么它必定也是会被缓存的,就和 computed 一样。

当然咱们这里的 getter 就是解决 state 数据。

4.6.1 增加 getter
咱们先来看一下如何定义 getter 吧,批改 user.ts。

代码如下:

export const useUsersStore = defineStore("users", {state: () => {
    return {
      name: "悟空",
      age: 25,
      sex: "男",
    };
  },
  getters: {getAddAge: (state) => {return state.age + 100;},
  },
});

上段代码中咱们在配置项参数中增加了 getter 属性,该属性对象中定义了一个 getAddAge 办法,该办法会默认接管一个 state 参数,也就是 state 对象,而后该办法返回的是一个新的数据。

4.6.2 应用 getter
咱们在 store 中定义了 getter,那么在组件中如何应用呢?应用起来非常简单,咱们批改 App.vue。

代码如下:

<template>
  <p> 新年龄:{{store.getAddAge}}</p>
  <button @click="patchStore"> 批量批改数据 </button>
</template>
<script setup lang="ts">
import {useUsersStore} from "../src/store/user";
const store = useUsersStore();
// 批量批改数据
const patchStore = () => {
  store.$patch({
    name: "张三",
    age: 100,
    sex: "女",
  });
};
</script>

上段代码中咱们间接在标签上应用了 store.gettAddAge 办法,这样能够保障响应式,其实咱们 state 中的 name 等属性也能够以此种形式间接在标签上应用,也能够放弃响应式。

当咱们点击批量批改数据按钮时,页面上的新年龄字段也会跟着变动。

4.6.3 getter 中调用其它 getter

后面咱们的 getAddAge 办法只是简略的应用了 state 办法,然而有时候咱们须要在这一个 getter 办法中调用其它 getter 办法,这个时候如何调用呢?

其实很简略,咱们能够间接在 getter 办法中调用 this,this 指向的便是 store 实例,所以天经地义的可能调用到其它 getter。

示例代码如下:

export const useUsersStore = defineStore("users", {state: () => {
    return {
      name: "悟空",
      age: 25,
      sex: "男",
    };
  },
  getters: {getAddAge: (state) => {return state.age + 100;},
    getNameAndAge(): string {return this.name + this.getAddAge; // 调用其它 getter},
  },
});

上段代码中咱们又定义了一个名为 getNameAndAge 的 getter 函数,在函数外部间接应用了 this 来获取 state 数据以及调用其它 getter 函数。

仔细的小伙伴可能会发现咱们这里没有应用箭头函数的模式,这是因为咱们在函数外部应用了 this,箭头函数的 this 指向问题置信大家都晓得吧!所以这里咱们没有采纳箭头函数的模式。

那么在组件中调用的模式没什么变动,代码如下:

<p> 调用其它 getter:{{store.getNameAndAge}}</p>

4.6.4 getter 传参
既然 getter 函数做了一些计算或者解决,那么咱们很可能会须要传递参数给 getter 函数,然而咱们后面说 getter 函数就相当于 store 的计算属性,和 vue 的计算属性差不多,那么咱们都晓得 Vue 中计算属性是不能间接传递参数的,所以咱们这里的 getter 函数如果要承受参数的话,也是须要做解决的。

示例代码:

export const useUsersStore = defineStore("users", {state: () => {
    return {
      name: "悟空",
      age: 25,
      sex: "男",
    };
  },
  getters: {getAddAge: (state) => {return (num: number) => state.age + num;
    },
    getNameAndAge(): string {return this.name + this.getAddAge; // 调用其它 getter},
  },
});

上段代码中咱们 getter 函数 getAddAge 接管了一个参数 num,这种写法其实有点闭包的概念在外面了,相当于咱们整体返回了一个新的函数,并且将 state 传入了新的函数。

接下来咱们在组件中应用,形式很简略,代码如下:

<p> 新年龄:{{store.getAddAge(1100) }}</p>

4.7 actions 属性
后面咱们提到的 state 和 getters 属性都次要是数据层面的,并没有具体的业务逻辑代码,它们两个就和咱们组件代码中的 data 数据和 computed 计算属性一样。

那么,如果咱们有业务代码的话,最好就是卸载 actions 属性外面,该属性就和咱们组件代码中的 methods 类似,用来搁置一些解决业务逻辑的办法。

actions 属性值同样是一个对象,该对象外面也是存储的各种各样的办法,包含同步办法和异步办法。

4.7.1 增加 actions
咱们能够尝试着增加一个 actions 办法,批改 user.ts。

代码如下:

export const useUsersStore = defineStore("users", {state: () => {
    return {
      name: "悟空",
      age: 25,
      sex: "男",
    };
  },
  getters: {getAddAge: (state) => {return (num: number) => state.age + num;
    },
    getNameAndAge(): string {return this.name + this.getAddAge; // 调用其它 getter},
  },
  actions: {saveName(name: string) {this.name = name;},
  },
});

上段代码中咱们定义了一个非常简单的 actions 办法,在理论场景中,该办法能够是任何逻辑,比方发送申请、存储 token 等等。大家把 actions 办法当作一个一般的办法即可,非凡之处在于该办法外部的 this 指向的是以后 store。

4.7.2 应用 actions
应用 actions 中的办法也非常简单,比方咱们在 App.vue 中想要调用该办法。

代码如下:

const saveName = () => {store.saveName("我是悟空");
};

咱们点击按钮,间接调用 store 中的 actions 办法即可。

5. 总结示例代码
后面的章节中的代码都不残缺,次要贴的是次要代码局部,咱们这节将咱们本篇文章用到的所有代码都贴出来,供大家练习。

main.ts 代码:

import {createApp} from "vue";
import App from "./App.vue";
import {createPinia} from "pinia";
const pinia = createPinia();
​
​
const app = createApp(App);
app.use(pinia);
app.mount("#app");

user.ts 代码:

import {defineStore} from "pinia";
​
​
// 第一个参数是应用程序中 store 的惟一 id
export const useUsersStore = defineStore("users", {state: () => {
    return {
      name: "悟空",
      age: 25,
      sex: "男",
    };
  },
  getters: {getAddAge: (state) => {return (num: number) => state.age + num;
    },
    getNameAndAge(): string {return this.name + this.getAddAge; // 调用其它 getter},
  },
  actions: {saveName(name: string) {this.name = name;},
  },
});

App.vue 代码:

<template>
  <img alt="Vue logo" src="./assets/logo.png" />
  <p> 姓名:{{name}}</p>
  <p> 年龄:{{age}}</p>
  <p> 性别:{{sex}}</p>
  <p> 新年龄:{{store.getAddAge(1100) }}</p>
  <p> 调用其它 getter:{{store.getNameAndAge}}</p>
  <button @click="changeName"> 更改姓名 </button>
  <button @click="reset"> 重置 store</button>
  <button @click="patchStore"> 批量批改数据 </button>
  <button @click="saveName"> 调用 aciton</button>
​
​
  <!-- 子组件 -->
  <child></child>
</template>
<script setup lang="ts">
import child from "./child.vue";
import {useUsersStore} from "../src/store/user";
import {storeToRefs} from "pinia";
const store = useUsersStore();
const {name, age, sex} = storeToRefs(store);
const changeName = () => {
  store.name = "张三";
  console.log(store);
};
// 重置 store
const reset = () => {store.$reset();
};
// 批量批改数据
const patchStore = () => {
  store.$patch({
    name: "张三",
    age: 100,
    sex: "女",
  });
};
// 调用 actions 办法
const saveName = () => {store.saveName("我是悟空");
};
</script>

child.vue 代码:

<template>
  <h1> 我是 child 组件 </h1>
  <p> 姓名:{{name}}</p>
  <p> 年龄:{{age}}</p>
  <p> 性别:{{sex}}</p>
  <button @click="changeName"> 更改姓名 </button>
</template>
<script setup lang="ts">
import {useUsersStore} from "../src/store/user";
import {storeToRefs} from 'pinia';
const store = useUsersStore();
const {name, age, sex} = storeToRefs(store);
const changeName = () => {store.name = "悟空";};
</script>

总结
pinia 的知识点很少,如果你有 Vuex 根底,那么学起来更是大海捞针。其实咱们更应该关注的是它的函数思维,大家有没有发现咱们在 Vue3 中的所有货色仿佛都能够用一个函数来示意,pinia 也是连续了这种思维。

所以,大家了解这种组合式编程的思维更重要,pinia 无非就是以下 3 个大点:

state
getters
actions
当然,本篇文章只是解说了根底应用局部,然而在理论工作中也能满足大部分需要了,如果还有趣味学习 pinia 的其它特点,比方插件、订阅等等,能够移步官网:pinia 官网。

退出移动版