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# 或者应用 npmnpm install pinia

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

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

代码如下:

// main.tsimport { 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.tsimport { defineStore } from 'pinia'// 第一个参数是应用程序中 store 的惟一 idexport 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>// 重置storeconst 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 的惟一 idexport 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);};// 重置storeconst 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官网。