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

Posted X可乐

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了这一定是你看过的最简单的 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 插件的处理等,后续有时间在更,对该案例有看不懂的地方可以评论区交流

以上是关于这一定是你看过的最简单的 pinia 源码的主要内容,如果未能解决你的问题,请参考以下文章

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

这可能是你看过最好的微服务架构详解文章

【pinia源码】一、createPinia源码解析

❤️十大排序算法详解❤️——可能是你看过最全的,完整版代码

HTTP—这估计是你看的最仔细的一份协议了!

听说你看过 ThreadLocal 源码?