# Pinia的使用

Pinia是VueX的新版本,pinia和Vuex的作用是一样的,它也充当的是一个存储数据的作用,存储在pinia的数据允许我们在各个组件中使用。

# 相对于VueX,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支持插件来扩展自身功能。
  • 支持服务端渲染。

# 小型状态管理工具的实现

利用vue3中提供的响应式数据函数 refreactive 就可以实现小型状态管理工具。

//store/index.ts
//小型状态管理工具的实现
import {ref} from 'vue'
import type {Ref} from 'vue'

const count: Ref<number> = ref(0)
const setCount = (num: number) => {
    count.value = num
}
export {count, setCount}
//A组件
<template>
  <div class="a-component">
    <div>A组件</div>
    <div>{{ count }}</div>
  </div>
</template>
<script lang="ts" setup>
  import {count} from './store'
</script>
//B组件
<template>
  <div class="b-component">
    <div>B组件</div>
    <button @click="setCount(count + 1)">点我+1</button>
  </div>
</template>
<script lang="ts" setup>
  import {setCount, count} from './store'
</script>

# Pinia的安装

和vue-router、vuex等一样,我们想要使用pinia都需要先安装它,安装它也比较简单。

# 安装命令

npm install pinia

# main.ts配置

安装完成后我们需要将pinia挂载到Vue应用中,也就是我们需要创建一个根存储传递给应用程序,简单来说就是创建一个存储数据的数据桶,放到应用程序中去。
修改main.js,引入pinia提供的createPinia方法,创建根存储。

import {createPinia} from "pinia";

const app = createApp(App);
app.use(createPinia());
app.mount("#app");

# 创建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的实现思想是一样的。

# 使用store

前面我们创建了一个store,说白了就是创建了一个方法,那么我们的目的肯定是使用它,假如我们要在App.vue里面使用它,该如何使用呢?

# 代码如下

// /src/App.vue
import {useUsersStore} from "../src/store/user";

const store = useUsersStore();
console.log(store);

# 添加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接收的是一个箭头函数返回的值,它不能直接接收一个对象。

# 操作state

# 读取state数据

读取state数据很简单,在App.vue中打印store即可

# 使用对象结构的方法获取store的数据

import {useUsersStore} from "../src/store/user";

const store = useUsersStore();
const {name, age, sex} = store;

# 修改state数据

如果我们想要修改store中的数据,可以直接重新赋值即可,我们在App.vue里面添加一个按钮,点击按钮修改store中的某一个数据。

# 响应式store数据

pinia提供了storeToRefs方法给我们,让我们获得的name等属性变为响应式的,

利用pinia的storeToRefs函数,将sstate中的数据变为了响应式的。

import {storeToRefs} from 'pinia';

const store = useUsersStore();
const {name, age, sex} = storeToRefs(store);

# 重置state

有时候我们修改了state数据,想要将它还原,这个时候该怎么做呢?就比如用户填写了一部分表单,突然想重置为最初始的状态。

此时,我们直接调用store的$reset()方法即可,继续使用我们的例子,添加一个重置按钮。

# 代码如下

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

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

# 批量更改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

pinia提供了方法让我们直接替换整个state对象,使用store的$state方法。

# 示例代码

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

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

# getters属性

getters是defineStore参数配置项里面的另一个属性,前面我们讲了state属性。getter属性值是一个对象,该对象里面是各种各样的方法。大家可以把getter想象成Vue中的计算属性,它的作用就是返回一个新的结果,既然它和Vue中的计算属性类似,那么它肯定也是会被缓存的,就和computed一样。

当然我们这里的getter就是处理state数据。

# 添加getter

# 代码如下

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

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

# getter的使用

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

# 代码如下


<template>
    <p>新年龄:{{store.getAddAge}}</p>
</template>
<script setup lang="ts">
    import {useUsersStore} from "../src/store/user";

    const store = useUsersStore();
</script>

上段代码中我们直接在标签上使用了store.gettAddAge方法,这样可以保证响应式,其实我们state中的name等属性也可以以此种方式直接在标签上使用,也可以保持响应式。

# 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指向问题相信大家都知道吧!所以这里我们没有采用箭头函数的形式。

# 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>

# actions属性

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

那么,如果我们有业务代码的话,最好就是卸载actions属性里面,该属性就和我们组件代码中的methods相似,用来放置一些处理业务逻辑的方法。

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

# actions的添加

我们可以尝试着添加一个actions方法,修改user.ts。

# 代码如下

export const useUsersStore = defineStore("users", {
    state: () => {
        //数据
    },
    getters: {
        //计算属性
    },
    actions: {
        saveName(name: string) {
            this.name = name;
        },
    },
});

上段代码中我们定义了一个非常简单的actions方法,在实际场景中,该方法可以是任何逻辑,比如发送请求、存储token等等。大家把actions方法当作一个普通的方法即可,特殊之处在于该方法内部的this指向的是当前store。

# actions的使用

使用actions中的方法也非常简单,比如我们在App.vue中想要调用该方法。

# 代码如下

const saveName = () => {
    store.saveName("我是小猪");
};
上次更新: 12/12/2023, 7:09:33 PM