Vue3 的状态管理库(Pinia)

前言:

在编写vue里的项目时,必须要用到状态管理库,我们始终绕不开对 Pinia 的使用 ,vue3 中对状态管理库进行了一些重要的更新,在这里分享给大家!


一、什么是 Pinai

  • PiniaVue 的专属状态管理库,它允许你跨组件或页面共享状态。
  • PiniaVuex4 的升级版,也就是 Vuex5
  • Pinia 极大的简化了 Vuex 的使用,是 Vue3 的新的状态管理工具
  • Piniats 的支持更好,性能更优, 体积更小,无 mutations,可用于 Vue2Vue3
  • Pinia 支持 Vue Devtools、 模块热更新和服务端渲染
  • Pinia 的官方地址为:https://pinia.vuejs.org/

二、安装与使用pinia

  1. 安装语法:npm install pinia
  2. 创建一个 pinia(根存储)并将其传递给应用程序
import {
    
     createApp } from 'vue'
import App from './App.vue'

// 引入 createPinia 函数
import {
    
     createPinia } from 'pinia'

const app = createApp(App)

// 使用 createPinia() 来创建 Pinia(根存储),并应用到整个应用中
app.use(createPinia())

app.mount('#app')
  1. src 文件下创建一个 store 文件夹,并添加 store.js 文件。

三、什么是 store

  1. store 是一个保存状态和业务逻辑的实体,它并不与你的组件树绑定;换句话说,它承载着全局状态;它有点像一个永远存在的组件,每个组件都可以读取和写入它
  2. store 它有三个概念,stategettersactions,我们可以l理解成组件中的 datacomputedmethods
  3. 在项目中的 src\store 文件夹下不同的 store.js 文件
  4. store 是用 defineStore(name, function | options) 定义的,建议其函数返回的值命名为 use...Store 方便理解
    1. 参数 name:名字,必填值且唯一
    2. 参数 function|options:可以是对象或函数形式
      • 对象形式【选项模式】,其中配置 stategettersactions 选项
      • 函数形式【组合模式,类似组件组合式 API 的书写方式】,定义响应式变量和方法,并且 return 对应的变量和方法;ref() 相当于 statecomputed() 相当于 gettersfunction() 相当于 actions

选项式:

import {
    
     defineStore } from 'pinia'

// 创建 store,并暴露出去
// 参数一:名字,必填值且唯一
// 参数二:选项式书写方式采用对象形式
export const useStore = defineStore('main', {
    
    
    state: () => ({
    
    
        // ……
    }), 
    getters: {
    
    
        // ……
    },
    actions: {
    
    
        // ……
    }
})

组合式:

import {
    
     defineStore } from 'pinia'
import {
    
     computed, ref } from 'vue'

// 创建 store,并暴露出去
// 参数一:名字,必填值且唯一
// 参数二:组合式书写方式采用函数形式
export const useStore = defineStore('main', () => {
    
       
    
    // ref 变量  --->  state
    // computed() 计算属性  --->  getters 
    // functions 函数  --->  actions

    return {
    
     
        // 暴露出去 变量,函数,计算属性即可
    }
})

四、state

statestore 的核心部分,主要存储的是共享的数据。


1. 定义 state

  1. store 采用的是选项式模式时,state 选项为函数返回的对象,在其定义共享的数据
  2. store 采用的是组合式模式时,在其函数内定义的 ref 变量,最终 return 出去来提供共享的数据

选项式:

import {
    
     defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
    
    
  	// 共享的数据,为函数返回的对象形式
    state: () => ({
    
    
        age: 27,
        level: 5,
        account: 'SD77842',
        nickname: '自古风流'
    })
})

组合式:

import {
    
    defineStore} from "pinia";
import {
    
    ref} from "vue";

export const useUserStore = defineStore('user', () => {
    
    
    const age = ref(27)
    const level = ref(5)
    const account = ref('SD77842')
    const nickname = ref('自古风流')
    
    return {
    
     age, level, account, nickname } // 将数据暴露出去,共享给需要的组件
})

2. 组件中访问 state

  1. 在选项式 API 组件中,可以使用 mapState(storeObj, array | object) 帮助器将状态属性映射为只读计算属性
    1. storeObj 引入的 store 对象
    2. array | object:字符串数组形式或者对象形式
      • 【字符串数组形式】直接将 storestate 的数据映射为当前组件的计算属性,但是不能自定义名称
      • 【对象形式时】key 为自定义当前组件的计算属性名,value 字符串形式,是 storestate 的共享数据

提示:

mapState() 函数映射到组件中的计算属性是只读的,如果想在组件中响应式修改 state 的数据,则应该选择 mapWritableState() 函数来映射计算属性

  1. 在组合式 API 组件中,直接引入对应的store,通过 store 对象直接获取和修改 state

提示:

如果想在组件中自定义变量来接收 store 中的 state 中共享的数据,我们可以这样做:

  • 使用 computed(() => store.dataName),具有响应式,但是只读形式
  • 使用 storeToRefs(store)store 解构想要的 state,具有响应式,可直接修改,可自定义名称

选项式:

<script>
import {
    
     mapState, mapWritableState } from 'pinia'
import {
    
     useUserStore } from '@/store/useUserStore'
import UserVue from '@/components/User.vue'

export default {
    
    
    components: {
    
     UserVue },
    computed: {
    
    
        // mapState:将 store 的 state 映射成当前组件的计算属性
        // 具有响应式,但是是只读
        // 字符串数组形式:不能自定义计算属性名
        // 对象形式:可以自定义计算属性名
        ...mapState(useUserStore, ['age', 'level']),
        ...mapState(useUserStore, {
    
    
            user_account: 'account',
            user_nickname: 'nickname'
        }),
        // mapWritableState 与 mapState 用法类似,区别:它可以响应式的读写映射的计算属性
        ...mapWritableState(useUserStore, ['account', 'nickname']),
        ...mapWritableState(useUserStore, {
    
    
            user_age: 'age',
            user_level: 'level'
        }),
    }
}
</script>

<template>

    <UserVue></UserVue>

    <h2>mapState 映射的计算属性</h2>
    
    <ul>
        <li>年龄:{
    
    {
    
     age }}</li>
        <li>等级:{
    
    {
    
     level }}</li>
        <li>账号:{
    
    {
    
     user_account }}</li>
        <li>昵称:{
    
    {
    
     user_nickname }}</li>
    </ul>

    <button @click="age += 10">更改年龄</button>
    |
    <button @click="user_nickname += '='">更改昵称</button>
    

    <hr>

               
    <h2>mapWritableState 映射的计算属性</h2> 
        
    <ul>
        <li>年龄:{
    
    {
    
     user_age }}</li>
        <li>等级:{
    
    {
    
     user_level }}</li>
        <li>账号:{
    
    {
    
     account }}</li>
        <li>昵称:{
    
    {
    
     nickname }}</li>
    </ul>

    <button @click="user_age += 10">更改年龄</button>
    |
    <button @click="nickname += '='">更改昵称</button>

</template>

组合式:

<script setup>
import {
    
     useUserStore } from '@/store/useUserStore'
import {
    
     storeToRefs } from 'pinia'
import {
    
     computed } from 'vue'
import UserVue from '@/components/User.vue'

// 获取 UserStore 实例
const user_store = useUserStore()

// 通过 computed() 将 store 中 state 映射成当前组件中的计算属性,具有响应性,但是是只读的
const user_age = computed(() => user_store.age)
const user_level = computed(() => user_store.level)
const user_account = computed(() => user_store.account)
const user_nickname = computed(() => user_store.nickname)

// storeToRefs 将 store 中 state 解构为组件的数据,具有响应性,还可以响应式修改
const {
    
     
    age, 
    level,
    account: userAccount,
    nickname: userNickname
} = storeToRefs(user_store)
</script>

<template>
    <UserVue></UserVue>
    <h2>从 store 直接取 state </h2>
    <ul>
        <li>年龄:{
    
    {
    
     user_store.age }}</li>
        <li>等级:{
    
    {
    
     user_store.level }}</li>
        <li>账号:{
    
    {
    
     user_store.account }}</li>
        <li>昵称:{
    
    {
    
     user_store.nickname }}</li>
    </ul>
    <button @click="user_store.age += 10">更改年龄</button>
    |
    <button @click="user_store.nickname += '='">更改昵称</button>

    <hr>
    <h2>computed 映射为计算属性</h2>
    <ul>
        <li>年龄:{
    
    {
    
     user_age }}</li>
        <li>等级:{
    
    {
    
     user_level }}</li>
        <li>账号:{
    
    {
    
     user_account }}</li>
        <li>昵称:{
    
    {
    
     user_nickname }}</li>
    </ul>
    <button @click="user_age += 10">更改年龄</button>
    |
    <button @click="user_nickname += '='">更改昵称</button>
    
    <hr>
    <h2>storeToRefs 解构成自己的数据</h2>
    <ul>
        <li>年龄:{
    
    {
    
     age }}</li>
        <li>等级:{
    
    {
    
     level }}</li>
        <li>账号:{
    
    {
    
     userAccount }}</li>
        <li>昵称:{
    
    {
    
     userNickname }}</li>
    </ul>
    <button @click="age += 10">更改年龄</button>
    |
    <button @click="userNickname += '='">更改昵称</button>

</template>

五、Getters

getters 是计算得到的新的共享数据,当依赖的数据发生变化时则重新计算,所以其他组件包括 store 自己不要直接对其修改。


1. 定义 Getters

  1. store 采用的是选项式模式时,getters 选项中声明的函数即为计算属性
    1. 在其函数内可通过 this 关键字来获取 store 实例,也可通过方法的第一个参数得到 store 实例
    2. 如果采用的是箭头函数的话,无法使用 this 关键字,为了更方便使用 store 中实例,可为其箭头函数设置第一个参数来获取 store 实例
  2. store 采用的是组合式模式时,可通过 computed() 函数通过计算得到新的数据,再将其 return 暴露出去即可。

选项式:

import {
    
     defineStore } from "pinia"

export const useUserStore = defineStore('user', {
    
    
    state: () => ({
    
    
        birthday: '1992-12-27',
        age: 30
    }),
    // 通过计算得到的新的共享的数据,只读
    // 如果依赖的数据发生变化,则会重新计算
    getters: {
    
    
        month() {
    
    
          	// this 为 store 实例,当然其函数的第一个参数也为 store 实例
            return this.birthday.split('-')[1] 
        },
        // 因箭头函数无法使用 `this`,函数的第一个参数为 store 实例
        ageStage: store => {
    
    
            if(store.age < 18) return '未成年'
            if(store.age < 35) return '青年'
            if(store.age < 50) return '中年'
            if(store.age >= 50) return '老年'
        }
    }
})

组合式:

import {
    
     defineStore } from "pinia"
import {
    
     computed, ref } from "vue"

export const useUserStore = defineStore('user', () => {
    
    
    const birthday = ref('1992-12-27')
    const age = ref(30)

    // 声明通过计算得到的共享数据,是只读的,如果依赖的数据发生变化则会重新计算
    const month = computed(() => {
    
    
        return birthday.value.split('-')[1]
    })

    const ageStage = computed(() => {
    
    
        if (age.value < 18) return '未成年'
        if (age.value < 35) return '青年'
        if (age.value < 50) return '中年'
        if (age.value >= 50) return '老年'
    })

    return {
    
     birthday, age, month, ageStage }

})

2. 在组件中使用 Getters

  1. 选项式 API 的组件中,访问 store 中的 getters 和访问 state 类似,同样可使用 mapState() 帮助器将 getters 属性映射为只读计算属性

注意:

如果采用 mapWritableState() 帮助器将 store 中的 getters 映射为组件内部的计算属性,依旧可以具有响应式,一旦对其进行修改则会报错

  1. 在组合式 API 组件中,访问 store 中的 getters 和访问 state 类似,直接引入对应的 store ,通过 store 对象直接获取 getters,但是如果对其进行修改则会报错

提示:

如果想将 store 中的 getter 中共享的数据映射为本地组件的计算属性,我们可以这样做:

  • 使用 computed(() => store.getterName),具有响应式,但是只读形式
    使用 storeToRefs(store)store 解构 getter 依旧是计算属性,所以是只读的,一旦对其进行修改则会报错,但是具有响应式,可自定义名称

选项式:

<script>
import {
    
     mapState, mapWritableState } from 'pinia'
import {
    
     useUserStore } from './store/useUserStore'

export default {
    
    
    computed: {
    
    
        // 从 store 中映射 getters 和映射 state 用法相同,都可以用 mapState
        // 具有响应式,但是是只读的,如果修改了则会警告
        ...mapState(useUserStore, ['month']),
        ...mapState(useUserStore, {
    
    
            user_age_stage: 'ageStage'
        }),
        // 从 store 中 映射 getters 和映射 state 用法相同,都可以用 mapWritableState
        // 具有响应式,但是是只读的,如果修改了则会报错
        ...mapWritableState(useUserStore, ['ageStage']),
        ...mapWritableState(useUserStore, {
    
    
            birthday_month: 'month'
        }),
        // 把 store 中 stage 解构为自己的计算属性
        ...mapWritableState(useUserStore, ['birthday', 'age'])
    }
}
</script>

<template>

    <h3>mapState 字符串数组形式将 getters 映射成计算属性</h3>
    <ul>
        <li>月份:{
    
    {
    
     month }}</li>
    </ul>

    <button @click="month = '5'">更改月份</button>

    <hr>

    <h3>mapState 对象形式将 getters 映射成计算属性</h3>
    <ul>
        <li>年龄阶段:{
    
    {
    
     user_age_stage }}</li>
    </ul>

    <button @click="user_age_stage = '未知'">更改年龄阶段</button>

    <hr>

    <h3>mapWritableState 字符串数组形式将 getters 映射成计算属性</h3>
    <ul>
        <li>年龄阶段:{
    
    {
    
     ageStage }}</li>
    </ul>

    <button @click="ageStage = '未知'">更改年龄阶段</button>

    <hr>

    <h3>mapWritableState 对象形式将 getters 映射成计算属性</h3>
    <ul>
        <li>月份:{
    
    {
    
     birthday_month }}</li>
    </ul>

    <button @click="birthday_month = '5'">更改年龄阶段</button>

    <hr>
    生日:<input type="date" v-model="birthday">
    |
    年龄:<input type="number" min="1" max="100" v-model="age">
</template>

组合式:

<script setup>
import {
    
     storeToRefs } from 'pinia'
import {
    
     computed } from 'vue'
import {
    
     useUserStore } from './store/useUserStore'

// store 实例,可直接通过 store 获取 getters, 但是是只读的,如果一旦修改则会报错
const user_store = useUserStore()
  

// 通过 computed 将 getters 映射为自己的计算属性, 但是是只读的,如果一旦修改则会警告
const birthday_month = computed(() => user_store.month)
const user_age_stage = computed(() => user_store.ageStage)
  

// 通过 storeToRefs 将 getters 解构为自己的计算属性, 但是是只读的,如果一旦修改则会警告
const {
    
     month, ageStage: userAgeStage } = storeToRefs(user_store)

  
// 将 state 解构为自己的数据
const {
    
     birthday, age } = storeToRefs(user_store)
  
</script>

<template>
  
    <h3>通过 store 直接获取 getters</h3>
    <ul>
        <li>月份:{
    
    {
    
     user_store.month }}</li>
        <li>年龄阶段:{
    
    {
    
     user_store.ageStage }}</li>
    </ul>

    <button @click="user_store.month = '5'">更改月份</button>
    |
    <button @click="user_store.ageStage = '未知'">更改年龄阶段</button>

    <hr>

    <h3>通过 computed 将 getters 映射为自己的计算属性</h3>
    <ul>
        <li>月份:{
    
    {
    
     birthday_month }}</li>
        <li>年龄阶段:{
    
    {
    
     user_age_stage }}</li>
    </ul>

    <button @click="birthday_month = '5'">更改月份</button>
    |
    <button @click="user_age_stage = '未知'">更改年龄阶段</button>

    <hr>

    <h3>通过 storeToRefs 将 getters 映射为自己的计算属性</h3>
    <ul>
        <li>月份:{
    
    {
    
     month }}</li>
        <li>年龄阶段:{
    
    {
    
     userAgeStage }}</li>
    </ul>

    <button @click="month = '5'">更改月份</button>
    |
    <button @click="userAgeStage = '未知'">更改年龄阶段</button>

    <hr>

    生日:<input type="date" v-model="birthday">
    |    
    年龄:<input type="number" min="1" max="100" v-model="age">
</template>

六、Actions

actions 一般情况下是对 state 中的数据进行修改的业务逻辑函数,actions 也可以是异步的,您可以在其中 await 任何 API 调用甚至其他操作!


1. 定义Actions

  1. store 采用的是选项式模式时,actions 选项中声明的函数即可共享其函数,在其函数内可通过 this 来获取整个 store 实例
  2. store 采用的是组合式模式时,可通过声明函数,再将其 return 暴露出去即可共享其函数

选项式:

import {
    
    defineStore} from "pinia"

export const useUserStore = defineStore('user', {
    
    
    state: () => ({
    
    
        nickname: '自古风流',
        age: 20
    }),
    // 定义共享的函数,其主要是修改 state 中的数据的逻辑代码
    // 其函数可以是异步的
    actions: {
    
    
        setUserInfo(nickname, age) {
    
    
            // 可通过 `this` 来获取当前 store 实例
            this.nickname = nickname
            this.age = age
        },
        setUserInfoByObject(user) {
    
    
            this.nickname = user.nickname
            this.age = user.age
        }
    }
})

组合式:

import {
    
    defineStore} from "pinia"
import {
    
    ref} from "vue";

export const useUserStore = defineStore('user', () => {
    
    
    const nickname = ref('自古风流')
    const age = ref(20)

    // 定义函数(注意:形参不要和 ref 名冲突)
    function setUserInfo(user_nickname, user_age) {
    
    
        nickname.value = user_nickname
        age.value = user_age
    }

    function setUserInfoByObject(user) {
    
    
        // 可通过 `this` 来获取当前 store 实例
        nickname.value = user.nickname
        age.value = user.age
    }

    return {
    
    nickname, age, setUserInfo, setUserInfoByObject} // 暴露函数即可共享函数
})

2. 组件中访问 Actions

  1. 在选项式 API 组件中,可以使用 mapActions(storeObj, array | object) 帮助器将 actions 映射为当前组件的函数
    1. storeObj 引入的 store 对象
    2. array | object:字符串数组形式或者对象形式
      • 【字符串数组形式】直接将 storeactions 的函数映射为当前组件的函数,但是不能自定义名称
      • 【对象形式时】key 为自定义当前组件的函数名,value 字符串形式,是 storeactions 的函数名
  2. 在组合式 API 组件中,直接引入对应的 store,通过 store 对象直接获取 actions

提示:

如果想将 store 中的 actions 中函数映射为本地组件的函数,可将 store 解构出对应的函数即可,也可自定应函数名,此处不可通过 storeToRefs(store) 函数

选项式:

<script>
import {
    
    mapActions, mapState} from "pinia"
import {
    
    useUserStore} from "@/store/useUserStore"

export default {
    
    
    computed: {
    
    
        ...mapState(useUserStore, ['nickname', 'age'])
    },
    methods: {
    
    
        // 使用 mapActions 将 store 中的 actions 映射为自己的函数
        // 采用函数形式,无法自定义映射的函数名
        // 采用对象形式,可自定义映射的函数名
        ...mapActions(useUserStore, ['setUserInfo']),
        ...mapActions(useUserStore, {
    
    
            set_info_by_object: 'setUserInfoByObject'
        })
    }
}
</script>

<template>
    <ul>
        <li>昵称:{
    
    {
    
     nickname }}</li>
        <li>昵称:{
    
    {
    
     age }}</li>
    </ul>
    
    <button @click="setUserInfo('Tom', 15)">修改信息</button>

    <button @click="set_info_by_object({ nickname: 'Jack', age: 40})">
        修改信息
    </button>
</template>

组合式:

<script setup>
import {
    
    useUserStore} from "@/store/useUserStore"
import {
    
     storeToRefs } from "pinia"

// 可直接使用 store 执行 actions
const user_store = useUserStore()
const {
    
    nickname, age} = storeToRefs(user_store)

// 可将 store 中的 actions 映射为自己的函数,可自定映射的函数名(不可使用 storeToRes 函数)
const {
    
    setUserInfo, setUserInfoByObject: set_user_info_object} = user_store
</script>

<template>
    <ul>
        <li>昵称:{
    
    {
    
     nickname }}</li>
        <li>昵称:{
    
    {
    
     age }}</li>
    </ul>
  
    <span>直接使用 store 执行 actions:</span>
    <button @click="user_store.setUserInfo('Tom', 15)">修改信息</button>
    <button @click="user_store.setUserInfoByObject({ nickname: 'Jack', age: 40})">
      	修改信息
    </button>

    <hr>

    <span>将 store 中的 actions 映射成自己的函数:</span>
    <button @click="setUserInfo('Annie', 45)">修改信息</button>
    <button @click="set_user_info_object({ nickname: 'Drew', age: 50})">
      	修改信息
    </button>
</template>

总结:

欢迎大家加入我的社区,在社区中会不定时发布一些精选内容:https://bbs.csdn.net/forums/db95ba6b828b43ababd4ee5e41e8d251?category=10003


以上就是 Vue3 的状态管理库(Pinia),不懂得也可以在评论区里问我或私聊我询问,以后会持续发布一些新的功能,敬请关注。
我的其他文章:https://blog.csdn.net/weixin_62897746?type=blog

猜你喜欢

转载自blog.csdn.net/weixin_62897746/article/details/129124364