这一定是你看过的最简单的 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
    

    选择 vue —> vue + ts

  • 项目中添加 pinia

    npm install pinia
    
  • 目录结构

    - src
      - api
      - assets
      - components
        - Home.vue
      - store
        - index.ts
    

    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');

  • 使用

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

// 在使用前需要先实例化
import {mainStore} from './../store/index'
const store = mainStore();
  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
    })
    
  5. $state

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

    在 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
              }
          }
      }
      
    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};
                  },
      }
      

源码剖析

  • 根据上边的基础使用我们可以知道,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
}

创建 defineStore

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

总结

  • 至此我们手写的简易版 pinia 已完成,该案例只实现了一些基础的功能,其他还有很多功能没有完善,如 actions 的处理,vue2/vue3 的处理,pinia 插件的处理等,后续有时间在更,对该案例有看不懂的地方可以评论区交流
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值