• 这一定是你看过的最简单的 pinia 源码


    为什么使用pinia?

    • pinia 是一种全局状态管理工具,其底层其实是对 Vue 自身状态的一种高度封装,本期我们主要讨论 pinia 配合 Vue3 的使用, 在 vue3 中, pinia 的全局状态也是基于 reactive 达成的一种 代理对象

    和 vuex 的区别?

    • 相对于 vuex,对 ts 的支持更加友好,具有可靠的类型推断。
    • 支持多个 store 协同。
    • 去除 mutations,只有 state,getters,actions。
    • 无模块嵌套,只有 store,store 之间可以自由使用,更好的代码分割;

    基础使用

    • 使用vite 构建项目

      npm init vite@latest my-vue-app -- --template vue
      
      • 1

      选择 vue —> vue + ts

    • 项目中添加 pinia

      npm install pinia
      
      • 1
    • 目录结构

      - src
        - api
        - assets
        - components
          - Home.vue
        - store
          - index.ts
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

      store 为全局仓库定义处,使用的时候 都需要从此处引入,可以多人维护一个,也可以每个人单独维护一个自己的 store,即在自己负责的组件或模块中,同此全局仓库的定义方法和使用方法相同,只在引入的时候使用不同的仓库名即可。

    • 挂载 pinia

    // 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');
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 使用

    引入的时候,一共有以下几种方法

    // 在使用前需要先实例化
    import {mainStore} from './../store/index'
    const store = mainStore();
    
    • 1
    • 2
    • 3
    1. 直接解构

      • 会失去响应式
      • 可以解构 actions 中的方法
    2. 使用 点语法访问

      • 可以配合计算属性做进一步处理
      • 可以访问到 getter 和 actions
    3. 使用 storeToRefs

      • 解构以后将其包装为响应式对象,调用时需要变量名后加 .value
      • 不能解构 actions 中的方法
    4. $path

      • 可以使用 此方法直接对 store中的多个数据进行更改,接收一个对象或一个回调函数,回调函数接收一个参数(state),可以分别对状态里的某个集合进行指定更改,而不是从新赋值,使用对象的形式只能从新赋值
      store.$patch({
        counter: store.counter + 1,
        name: 'Abalam',
      })
      cartStore.$patch((state) => {
        state.items.push({ name: 'shoes', quantity: 1 })
        state.hasChanged = true
      })
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
    5. $state

      • 用于替换整个状态, 也可以使用 store.state.value = {} 来替换
      store.$state = { counter: 666, name: 'Paimon' }
      store.state.value = {}
      
      • 1
      • 2
    • 方法

      在 getter 和actions 中 都可以通过 this 访问到当前 pinia中的 其他 getter 和 actions中的函数,实现相互调用

      1. pinia中的 getter

        • getter 和 Vue 中的计算属性几乎一样,在获取 State 值之前做一些逻辑处理, 会缓存上一次的值,如果没有更新,则只会调用一次
        • 使用的时候 getter 和 state 导入的方法是一样的,因为getter 只是将我们在其中定义的变量包装了一层 proxy 打入到 state 中,但 如果 getter 使用了高阶函数,返回了一个函数, 在使用的时候,导入方法同 actions
        • 通过以下第二个方式的调用,也可以实现通过传值的形式,对store中的值进行修改,但不建议这样做,修改的逻辑尽量还是放在actions中
         // 通过getter 获得计算以后的数据
        getters: {
            // 获取计算以后的属性
            getData(state):boolean{
                return !this.data.msg
            },
            // 通过传值的方式获取计算以后的属性
            getCurrentData(state){
                return (bool:boolean) => {
                    return this.data.msg = bool
                }
            }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
      2. pinia中的 actions

        • 支持异步async的使用,通过 this的方式直接访问 state中定义的状态及修改状态
        import { mainStore } from '../../material_show/pinia'
        actions: {
            setMainStore() {
                //可以直接引入其他仓库的pinia,获取其值或调用getter和actions
                ++mainStore().stuRefresh;
                ++mainStore().parRefresh;
            },
                // 更改用户登录状态
                changeUserStatus(bool:boolean){
                    this.userStatus = bool;
                },
                    // 更新教师信息
                    updateTeacher(options:teacherType){
                        this.teacherDetail = {...options};
                    },
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16

    源码剖析

    • 根据上边的基础使用我们可以知道,pinia 的使用分为 3 步
      • 通过 createPinia 在 vue 实例化前挂载到 vue 上
      • 通过 defineStore 配置为 store 仓库
      • 通过 defineStore 的返回值,获取到 store 中的 state/getters/actions

    创建 createPinia

    • 函数内部对外暴露一个对象
    • 对象内部提供一个 install 方法,该方法会在 app.use(pinia) 时执行,且会传递一个参数为 vue 自身
    • install 内部只需要将pinia 通过 provide 暴露出去,以供所有子组件使用
    • 对象内部除 install 方法外还有两个属性为 state 与 _s,state 用于存储 store 仓库的数据,_s 用于存储 store,如多人开发时可能会每人维护一个 pinia
    const piniaSymbol = Symbol('pinia')
    function createPinia() {
        let pinia = {
            install(app) {
                app.provide(piniaSymbol, pinia)
            },
            state: ref({}),  // store 中的 state
            _s: new Map() // 存储每一个 store
        }
        return pinia
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    创建 defineStore

    • 在上文提到,defineStore 接受两个参数,一个字符串,一个配置对象(还有其他配置方法,效果相同本文不做探讨)
    • 该方法内部返回一个对象,对象在组件中调用后可以通过点语法调用state/getters/actions
    function defineStore(id, options) {
        function useStore() { ... }
        return useStore
    }
    
    • 1
    • 2
    • 3
    • 4

    创建 useStore

    • 通过 defineStore 我们可以知道该方法内部在调用成功后,可以通过点语法获取到 state/getters/actions
    • 所以该方法内部会先注入 pinia
    function useStore() {
        let pinia = inject(piniaSymbol)    // piniaSymbol 为在 createPina 代码段内声明的全局变量
        if (!pinia._s.get(id)) {    // 当 pinia._s.get(id) 获取不到值时在判断内部设置上
            // 将 store 存储到 pinia._s 内,并合并state/getters/actions 到一个对象中
            createOtionsStore(id, options, pinia)
        }
        let store = pinia._s.get(id)    // 获取到处理后 store 将其 return 
        return store
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    创建 createOptionsStore
    • 通过 useStore 方法我们可以确定 createOptionsStore 的功能有两个
      • 将 state/getters/actions 合并到一个对象上
      • 将该对象存储到 pinia._s 中
    function createOtionsStore(id, options, pinia) {
        let store = reactive({
            _p: pinia,
            id
        })    // 创建 store
        Object.assign(store, setup())    // 将 setup 的返回值通过 Object.assign 合并到 store 对象中
        pinia._s.set(id, store)    // 将 处理后的 store 存储到 pinia._s 中
        function setup() { ...... }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    创建 setup
    • 看完 createOptionsStore 的方法相比你也肯定可以联想到 setup 就是将 state/getters/actions 合并到一个对象中
    • 此处 actions 源码过多,所以没有处理
    function setup() {
        // 将 需要用到的 state/getters/actions 从 options 中解构出来
        const { state, getters, actions } = options
        // 判断 pinia.state.value 中是否有值,如果没值则将 state 的调用结果赋值进去
        !pinia.state.value[id] && (pinia.state.value[id] = state())
        // 此时通过 toRefs 将 pinia.state.value[id] 处理为响应式的,暂存与 localState
        let localState = toRefs(pinia.state.value[id])
        
        // 处理getters 时先拿到内部所有的 key 组成的数组,并遍历给其包装为 computed
        let localGetters = Object.keys(getters).reduce((item, name) => {
            item[name] = computed(() => {
                const store = pinia._s.get(id)
                // 通过 call 将 getters 内部方法的 this 指向了store,并给其传递了唯一的参数 也就是 store 自身 
                return getters[name].call(store, store)
            })
            return item
        }, {})
        
        // 此处将 处理后的 state/getters/actions 返回
        return Object.assign(localState, localGetters, actions)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    总结

    • 至此我们手写的简易版 pinia 已完成,该案例只实现了一些基础的功能,其他还有很多功能没有完善,如 actions 的处理,vue2/vue3 的处理,pinia 插件的处理等,后续有时间在更,对该案例有看不懂的地方可以评论区交流
  • 相关阅读:
    基于nodejs+vue视频网站的设计与实现mysql
    【MySQL】19-MySQL中如何创建数据库和管理数据库
    Spring IOC 容器:掌握 Spring 的核心技术
    VBA技术资料MF70:从单元格文本中取消或删除上标
    TGI 基准测试
    去哪儿网2023正式秋招啦,来这里可以内推
    golang——接口
    python函数(8):异常处理机制+模块
    sql:SQL优化知识点记录(九)
    day01-4-订座功能
  • 原文地址:https://blog.csdn.net/CS_DGD/article/details/125558728