• Vue——状态管理库Pinia


    写在前面:本文参考小满大牛的pinia专栏

    一、Vuex与Pinia

    Vuex 和 Pinia 均是 Vue.js 的状态管理库,它们为 Vue 应用程序提供了一种集中式的、可预测的状态管理解决方案。

    Vuex 是 Vue.js 官方推荐的状态管理库之一。它的核心概念包括 state、mutation、action 和 getter。其中,state 代表应用程序的状态数据,在 Vuex 中存储为唯一的来源,mutation 用于修改状态数据并确保数据变化的可追踪性,action 用于处理异步操作或组合多个 mutation 操作,getter 可以让我们对 state 进行计算和派生,并使其变得更加易于访问。一个 Vuex store 实例是一个全局 JavaScript 对象,可以在所有组件中通过注入来进行访问和操作。

    Pinia 是一个新的状态管理库,也是专门为 Vue 3 开发的。它提供了一个类似于 Vuex 的状态管理模式,但采用最新的 Vue 3 API 构建。相比 Vuex,Pinia 更简单、更轻量,更加灵活,并支持 TypeScript 类型检查。

    与 Vuex 相比,Pinia 没有严格的命名约定,可以自由拆分逻辑、支持独立实例、执行更轻量级的代码等。但不同于 Vuex,它并没有内置支持模块化和严格调试工具

    以下是Pinia的特点

    • 完整的 ts 的支持;
    • 足够轻量,压缩后的体积只有1kb左右;
    • 去除 mutations,只有 state,getters,actions;
    • actions 支持同步和异步;
    • 代码扁平化没有模块嵌套,只有 store 的概念,store 之间可以自由使用,每一个store都是独立的
    • 无需手动添加 store,store 一旦创建便会自动添加;
    • 支持Vue3 和 Vue2

    Pinia官方文档

    二、安装、引入Pinia

    安装:npm install piniayarn add pinia
    引入注册:
    Vue3的引入方法
    main.ts

    import { createApp } from 'vue'
    import App from './App.vue'
    // Vue3的引入方法
    import {createPinia} from 'pinia'
    
    const store = createPinia()
    
    let app = createApp(App)
     
    app.use(store)
    
    app.mount('#app')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    Vue2的引入方法
    main.ts

    import { createPinia, PiniaVuePlugin } from 'pinia'
     
    Vue.use(PiniaVuePlugin)
    const pinia = createPinia()
     
    new Vue({
      el: '#app',
      pinia,
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    三、初始化创建Store

    新建文件夹
    在这里插入图片描述
    index.ts用于管理仓库,store-name.ts用于存储所有枚举的仓库名。
    store-name.ts

    // 枚举所有仓库名并暴露
    export const enum Names {
        TEST = 'TEST'
    }
    
    • 1
    • 2
    • 3
    • 4

    index.ts

    // 导入定义仓库的方法
    import { defineStore } from "pinia";
    // 导入枚举的所有仓库名
    import { Names } from "./store-name";
    
    // defineStore()定义一个仓库,第一个参数作为名称,也可看作是id
    // 这个id(名称)是必要的,Pinia使用它来讲store连接DevTools,可以通过调试工具查看
    export const useTestStore = defineStore(Names.TEST, {
        state: () => {
            return {
                // 定义初始化的值
                current: 1,
                name: '小5'
            }
        },
        // 类似于computed可以帮我们修饰我们的值
        getters: {
    
        },
        // 可以操作异步 和 同步 提交state
        actions: {
    
        }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    简单的使用仓库的数据
    App.vue

    <template>
      <div>pinia-current:{{ Test.current }}</div>
      <div>pinia-name:{{ Test.name }}</div>
    </template>
    
    <script setup lang="ts">
    import { useTestStore } from "./store";
    
    const Test = useTestStore();
    </script>
    
    <style lang="scss" scoped></style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    结果展示:
    在这里插入图片描述

    四、修改State值的五种方式

    1.直接修改

    State是允许不在仓库中直接修改值的,这与Vuex不同,Vuex要通过commitdispatch方法调用仓库修改。

    <template>
      <div>pinia-current:{{ Test.current }}</div>
      <div>pinia-name:{{ Test.name }}</div>
      <div style="display: flex; flex-direction: column">
        <button @click="change">change</button>
      </div>
    </template>
    
    <script setup lang="ts">
    import { useTestStore } from "./store";
    
    const Test = useTestStore();
    
    const change = () => {
      Test.current++;
    };
    </script>
    
    <style lang="scss" scoped></style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    2.批量修改

    在仓库的实例上有$patch方法可以批量修改多个值

    <template>
      <div>pinia-current:{{ Test.current }}</div>
      <div>pinia-name:{{ Test.name }}</div>
      <div style="display: flex; flex-direction: column">
        <button @click="bulkChange">批量change</button>
      </div>
    </template>
    
    <script setup lang="ts">
    import { useTestStore } from "./store";
    
    const Test = useTestStore();
    
    const bulkChange = () => {
      Test.$patch({
        current: 888,
        name: "小4",
      });
    };
    </script>
    
    <style lang="scss" scoped></style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    3.批量修改工厂函数形式

    推荐使用函数形式 可以自定义修改逻辑

    <template>
      <div>pinia-current:{{ Test.current }}</div>
      <div>pinia-name:{{ Test.name }}</div>
      <div style="display: flex; flex-direction: column">
        <button @click="funChange">工厂函数实现批量change</button>
    </template>
    
    <script setup lang="ts">
    import { useTestStore } from "./store";
    
    const Test = useTestStore();
    
    const funChange = () => {
      Test.$patch((state) => {
        (state.current = 999), (state.name = "小3");
      });
    };
    </script>
    
    <style lang="scss" scoped></style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    4.通过原始对象修改整个实例

    $state您可以通过将store的属性设置为新对象来替换store的整个状态

    缺点就是必须修改整个对象的所有属性,可以用结构赋值的方式解决这个缺点。

    <template>
      <div>pinia-current:{{ Test.current }}</div>
      <div>pinia-name:{{ Test.name }}</div>
      <div style="display: flex; flex-direction: column">
        <button @click="allChange">必须修改全部的写法(不推荐写法)</button>
      </div>
    </template>
    
    <script setup lang="ts">
    import { useTestStore } from "./store";
    
    const Test = useTestStore();
    
    const allChange = () => {
      Test.$state = {
        ...Test.$state,
        name: "小2",
      };
    };
    </script>
    
    <style lang="scss" scoped></style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    5.通过actions修改

    在仓库中定义Actions
    在仓库的actions 中直接使用this就可以指到state里面的值
    store/index.ts

    import { defineStore } from "pinia";
    import { Names } from "./store-name";
    
    export const useTestStore = defineStore(Names.TEST, {
        state: () => {
            return {
                current: 1,
                name: '小5'
            }
        },
        getters: {
    
        },
        // 可以操作异步 和 同步 提交state
        actions: {
            // 不能写箭头函数 否则this会指向错误
            setCurrent(num:number) {
                this.current = num
            }
        }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    直接在App.vue实例中调用

    <template>
      <div>pinia-current:{{ Test.current }}</div>
      <div>pinia-name:{{ Test.name }}</div>
      <div style="display: flex; flex-direction: column">
        <button @click="actionsChange">使用actions修改</button>
      </div>
    </template>
    
    <script setup lang="ts">
    import { useTestStore } from "./store";
    
    const Test = useTestStore();
    
    const actionsChange = () => {
      Test.setCurrent(555);
    };
    </script>
    
    <style lang="scss" scoped></style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    6.结果展示

    App.vue

    <template>
      <div>pinia-current:{{ Test.current }}</div>
      <div>pinia-name:{{ Test.name }}</div>
      <div style="display: flex; flex-direction: column">
        <button @click="change">change</button>
        <button @click="bulkChange">批量change</button>
        <button @click="funChange">工厂函数实现批量change</button>
        <button @click="allChange">必须修改全部的写法(不推荐写法)</button>
        <button @click="actionsChange">使用actions修改</button>
      </div>
    </template>
    
    <script setup lang="ts">
    import { useTestStore } from "./store";
    
    const Test = useTestStore();
    
    const change = () => {
      Test.current++;
    };
    const bulkChange = () => {
      Test.$patch({
        current: 888,
        name: "小4",
      });
    };
    const funChange = () => {
      Test.$patch((state) => {
        (state.current = 999), (state.name = "小3");
      });
    };
    const allChange = () => {
      Test.$state = {
        ...Test.$state,
        name: "小2",
      };
    };
    const actionsChange = () => {
      Test.setCurrent(555);
    };
    </script>
    
    <style lang="scss" scoped></style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    结果展示:
    在这里插入图片描述

    五、解构store

    在Pinia是不允许直接解构state的数据的,数据会失去响应式。

    const Test = useTestStore()
    // 直接解构失去响应式
    const { current, name } = Test
    
    • 1
    • 2
    • 3

    差异对比:

    <template>
      <div>响应式的值:{{ Test.current }}</div>
      <div>解构出的非响应式值:{{ current }}--{{ name }}</div>
      <button @click="change">change</button>
    </template>
    
    <script setup lang="ts">
    import { useTestStore } from "./store";
    
    const Test = useTestStore();
    const { current, name } = Test;
    const change = () => {
      Test.current++;
    };
    </script>
    
    <style lang="scss" scoped></style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    结果展示:
    在这里插入图片描述
    解决方案:
    使用pinia的storeToRefs()方式将数据响应式化。如下:

    // 引入
    import { storeToRefs } from "pinia";
    import { useTestStore } from "./store";
    
    const Test = useTestStore();
    // 响应式化
    const { current, name } = storeToRefs(Test);
    const change = () => {
      Test.current++;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    结果展示:
    在这里插入图片描述

    六、actions和getters

    store/index.ts

    import { defineStore } from "pinia";
    import { Names } from "./store-name";
    
    type User = {
        name: string,
        age: number
    }
    
    let result:User = {
        name: '小5',
        age: 18
    }
    
    const Login = (): Promise<User> => {
        return new Promise((resolve) => {
            setTimeout(() => {
                resolve({
                    name: "小4",
                    age: 17
                })
            })
        })
    }
    
    export const useTestStore = defineStore(Names.TEST, {
        state: () => {
            return {
                // 类型断言
                // {} 通常用于类型断言,表示将某个值强制转换成指定的类型。
                user: <User>{},
                name: 'xiao5'
            }
        },
        // 类似于computed可以帮我们修饰我们的值
        getters: {
            newName(): string {
                return `$-${this.name}`
            }
        },
        // 可以操作异步 和 同步
        actions: {
            // 不能写箭头函数 否则this会指向错误
            // 同步
            setUser1() {
                this.user = result
                this.name = '小8'
            },
            // 异步
            async setUser2() {
                const result = await Login()
                this.user = result
                this.name = '小7'
            },
        }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    App.vue

    <template>
      <div>actions:{{ Test.user }}</div>
      <button @click="change1">同步</button>
      <button @click="change2">异步</button>
      <div>getters:{{ Test.newName }}</div>
    </template>
    
    <script setup lang="ts">
    import { useTestStore } from "./store";
    
    const Test = useTestStore();
    const change1 = () => {
      Test.setUser1();
    };
    const change2 = () => {
      Test.setUser2();
    };
    </script>
    
    <style lang="scss" scoped></style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    结果展示:
    在这里插入图片描述
    另外,多个actions可以相互调用,多个getters也可以相互调用。

    七、API

    1.$reset

    重置store到它的初识状态

    import { useTestStore } from "./store";
    const Test = useTestStore();
    const reset = () => {
      Test.$reset();
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.$subscribe

    用于订阅state的改变,只要有state的变化就会触发这个函数
    $subscribe的第一个参数是个回调函数

    Test.$subscribe((args, state) => {
      console.log("======>", args);
      console.log("======>", state);
    });
    
    • 1
    • 2
    • 3
    • 4

    返回值args主要包括effecttargetstoreId等信息,state是state数据变化后的状态。如下图
    在这里插入图片描述
    第二个参数是是一个配置对象

    Test.$subscribe(
      (args, state) => {
        console.log("======>", args);
        console.log("======>", state);
      },
      {
        detached: true,
        deep: true,
        flush: "post",
      }
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    这三种配置详细如下:
    detached(脱离状态):指组件从其父级组件或 DOM 树中被移除的状态。在这种状态下,组件不再接收更新,并且可以被销毁。Vue 2.x 中通过调用 $destroy() 方法来销毁组件,Vue 3.x 中则使用 teleport、keepAlive 等组合来控制组件的生命周期。

    deep(深度监听):指对一个对象进行深度监听,在该对象的所有属性的值发生改变时,都能够得到通知。在 Vue.js 中,可以使用 vm.$watch() 方法来实现对数据的深度监听,Vue 3.x 中也提供了相应的 API 实现深度监听。

    flush(刷新策略):指一种更新数据后如何刷新页面的策略。在 Vue.js 中,默认的刷新策略是异步批处理模式(nextTick 模式),即将所有数据的更新操作放入一个队列中,在下一个 tick 执行更新操作,以减少不必要的 DOM 操作和提高性能。除此之外,Vue.js 还支持同步刷新(sync)、立即刷新(pre)等刷新策略。

    3.$onAction

    用于订阅actions的调用,只要有actions被调用就会触发这个函数。

    Test.$onAction((args) => {
      console.log(args);
    });
    
    • 1
    • 2
    • 3

    返回值args主要包括after回调,args(actions传递的参数),name(触发的actions名字),onError(错误回调),store(store实例)等。如下图
    在这里插入图片描述

    八、pinia插件

    pinia 和 vuex 都有一个通病 页面刷新状态会丢失

    我们可以写一个pinia 插件缓存他的值
    参考博客:满哥牛

  • 相关阅读:
    python 将一列文本或字符串调整成一行 中间用逗号 单引号分隔
    golang redis lua脚本 和 lua function
    宇泰(UTEK)5口全电口以太网交换机工业级导轨UT-LLDC-5TDS-24正品
    【QT+QGIS跨平台编译】之五十四:【QGIS_CORE跨平台编译】—【qgssqlstatementlexer.cpp生成】
    LeetCode知识点总结 - 1985
    首饰饰品经营商城小程序的作用是什么
    简单个人网页设计作业 静态HTML个人主题网页作业 DW个人网站模板下载 大学生简单个人网页作品代码 个人网页制作 学生个人网页Dreamweaver设计作业
    详解前端登录流程:实现原理与最佳实践
    flutter库
    基于阿里云oss的文件管理系统
  • 原文地址:https://blog.csdn.net/weixin_43599321/article/details/130849827