Pinia部分源码浅析

Pinia 部分源码浅析

概述

Pinia 中只用到了 vue-demi 一种库,vue-demi 的介绍可以参考 vue-demi。

Pinia可以在 vue2 和 vue3 中用于数据或状态的管理,同时 pinia 还提供了极其丰富的浏览器调试插件工具,更多详细内容可以参考 Pinia 官网

vue2 中使用 Pinia

pinia提供了PiniaVuePlugin,在 vue2 中需要手动注册

import Vue from "vue";
import { PiniaVuePlugin, createPinia } from "pinia";

Vue.use(PiniaVuePlugin);
const pinia = createPinia();

new Vue({
  el: "#app",
  // ...
  pinia,
});
PiniaVuePlugin 原理

PiniaVuePlugin是一个函数,注册时会被作为 install 方法,接收传入的参数 Vue,然后再通过Vue.mixin全局混入breforeCreatedestroyed这两个生命周期钩子函数,影响到每一个 Vue 实例。

breforeCreate

在这个钩子中 将选项中的 pinia 实例挂载到 vue 上。

  beforeCreate() {
   ...
    this._provided[piniaSymbol]=pinia
    this.$pinia = pinia;
    pinia._a=this //将vue挂载到pinia上,方便于pinia注册插件
   ...
  }
destroyed

在销毁组件时,将组件实例从 pinia 实例中移除。

vue3 中使用 Pinia

vue3 中使用 Pinia 需要先调用createPinia创建 Pinia 实例,返回 pinia 对象,再手动注册 Pinia 实例。

createPinia 创建 Pinia 实例

createPinia函数是Pinia的核心函数,它返回一个Pinia实例,该实例包含install方法,用于安装Pinia插件,以及use方法,用于注册插件,比如数据持久化插件piniaPluginPersistedstate

createPinia首先通过vueDemi.effectScope创建一个独立的作用域scope,再通过scope.run方法返回一个vueDemi.ref({}) ref 对象作为state

由上可知 vue2 中使用Pinia也会调用createPinia方法创建实例,因此在install属性方法中 Pinia 判断了当前环境是否是 vue2,如果不是,则执行app.config.globalProperties.$pinia = pinia;将 pinia 实例挂载到 vue 实例上

 export function createPinia() {
     const pinia =vueDemi.markRaw({
         install:(app)=>{/*...*/},
         use:(plugin)=>{/*...*/},
         _p,// 需要通过pinia.use的插件集合
         _a:null // 指向vue
         _e:scope,//独立作用域
         _s:new Map(),
         state, // 全局ref对象
     }
     return pinia
 }
defineStore 定义 Store

defineStore 定义 store,接受一个唯一的 idsetup和 配置项setupOptions,其中定义并返回了useStore函数,

如果 Pinia 中没有定义了同样的id,就判断参数setup是否是一个函数,如果是函数,则调用createSetupStore,否则调用createOptionsStore创建 store。
如果 Pinia 中已经定义了同名id的 store,否则通过id获取 store 并返回。

createSetupStore

createSetupStore 函数是 Pinia 中的核心部分, 接收 6 个参数,分别是id,setup,options,pinia,hot,isOptionsStorecreateSetupStore函数返回一个 store,这个 store 是一个对象,包含$id,$state,$patch,$reset,$subscribe,$subscribeWith,$dispose等方法。

createOptionsStore
storeToRefs

storeToRefs接收一个参数store,作用是从store中解构出响应式数据。

pinia 内部实现storeToRefs的逻辑是先判断当前环境是否是 vue2,
如果是 vu2,则通过vueDemi.toRef工具函数将 store 转为响应式数据,然后返回;
如果当前是 vue3,则分为两步:

  1. 定义一个空对象 refs,通过vueDemi.toRaw将 store 转为普通对象并返回
  2. 循环遍历第 1 步中的对象,通过vueDemi.isRefvueDemi.isReactive判断其值是否是响应式数据,如果是,则通过vueDemi.toRef将响应式数据转为普通数据,然后返回;
  3. 返回 refs
mapStores

mapStores 允许在不使用组合式 API (setup()) 的情况下使用存储(stores),通过生成一个对象来在组件的computed 字段中进行扩展。它接受多个 store 实例,并返回一个对象,其中每个属性都对应一个 store 实例。我们可以通过 store 的 id+Store 来访问

用法示例

export default {
  computed: {
    ...mapStores(useUserStore, useCartStore),
  },
  created() {
    this.userStore.fetchUser(); //eg store with id "user"
    this.cartStore.fetchCart(); //eg store with id "cart"
  },
};

mapStores 的实现很简单,遍历参数,然后返回一个对象,key 是每一个 store 的 id 加上后缀Store,value 是 store。后缀Store是 Pinia 默认的,我们也可以通过 Pinia 暴露的setMapStoreSuffix方法设置这个后缀。

function mapStores(...stores) {
  return stores.reduce((reduced, useStore) => {
    // @ts-expect-error: $id is added by defineStore
    reduced[useStore.$id + mapStoreSuffix] = function () {
      return useStore(this.$pinia);
    };
    return reduced;
  }, {});
}
mapState

mapState,允许在不适用组合式 API 的情况下使用 state,接收两个参数,第一个参数是 store,第二个参数是 store 的 key。key 可以是字符串数组也可以是对象,如果是对象,对象的 key 是函数的情况下,则可能在 ts 中使用时报错,因为由于某种原因,TS 无法将 storeKey 的类型推断为函数。

mapState等价于mapGetters,不过是mapGetters被 deprecated 弃用了。

用法示例如下

/*
state() => {
  return {
    count: 0,
    message:"hello"
  }
}
*/

export default defineComponent({
  computed: {
    ...mapState(useCounterStore(), ["count", "message"]),
  },
  created() {
    console.log(this.count, this.mesaage);
  },
});

源码如下

function mapState(useStore, keysOrMapper) {
  return Array.isArray(keysOrMapper)
    ? keysOrMapper.reduce((reduced, key) => {
        reduced[key] = function () {
          return useStore(this.$pinia)[key];
        };
        return reduced;
      }, {})
    : Object.keys(keysOrMapper).reduce((reduced, key) => {
        // @ts-expect-error
        reduced[key] = function () {
          const store = useStore(this.$pinia);
          const storeKey = keysOrMapper[key];
          // for some reason TS is unable to infer the type of storeKey to be a
          // function
          return typeof storeKey === "function"
            ? storeKey.call(this, store)
            : store[storeKey];
        };
        return reduced;
      }, {});
}
mapWriteableState

mapWriteableState允许在不使用组合式 API 的情况下,修改设置 state。因为 state 返回的是一个对象,可以利用重写对象的set设置其值
其内部实现如下

function mapWritableState(useStore, keysOrMapper) {
  return Array.isArray(keysOrMapper)
    ? keysOrMapper.reduce((reduced, key) => {
        // @ts-ignore
        reduced[key] = {
          get() {
            return useStore(this.$pinia)[key];
          },
          set(value) {
            // it's easier to type it here as any
            return (useStore(this.$pinia)[key] = value);
          },
        };
        return reduced;
      }, {})
    : Object.keys(keysOrMapper).reduce((reduced, key) => {
        // @ts-ignore
        reduced[key] = {
          get() {
            return useStore(this.$pinia)[keysOrMapper[key]];
          },
          set(value) {
            // it's easier to type it here as any
            return (useStore(this.$pinia)[keysOrMapper[key]] = value);
          },
        };
        return reduced;
      }, {});
}
mapActions

同上,通过mapActions可以访问使用到 store 实例的 action 方法。mapActions内部实现和mapState逻辑大同小异。

使用示例如下

export default {
  methods: {
    ...mapActions(useCounterStore, ["increment"]),
  },
};
skipHydrateshouldHydrate

skipHydrate函数用于在热更新时跳过 hydrate 阶段。hydrate,即水合,在 vue3 中,hydrate是一个用于将 VNode 树转化为真实 DOM 的过程,它尝试复用服务器端渲染(SSR)生成现有 DOM 结构,这个过程是在客户端启动时进行的,目的是加快页面的渲染速度,减少首屏加载时间。

内部实现原理是首先判断当前环境,如果是 vue3,则给对象加一个skipHydrateSymbol属性,反之给skipHydrateMap追加一个键值对[obj]:1,这样通过shouldHydrate判断是否需要刷新该对象。

shouldHydrate在通过createSetupStore创建 store 时会调用,判断是否需要进行水合

acceptHMRUpdate 热模块替换函数

acceptHMRUpdate函数接收两个参数,第一个参数是store,第二个参数是hot,即import.meta.hotacceptHMRUpdate函数返回一个函数,这个函数接收一个参数,这个参数是store,这个函数会判断store是否是import.meta.hotmodule.$id,如果是,则返回store,否则返回null

内部实现原理:

  • 订阅 HMR 更新:Pinia 在创建 store 实例时,会订阅 webpack 的 HMR API,以便在更新时接收到通知。

  • 保存先前的状态:在接收到 HMR 更新之前,Pinia 会首先保存当前的状态。

  • 应用更新:当收到 HMR 更新通知时,Pinia 会调用 acceptHMRUpdate 方法。

  • 恢复状态:在应用更新之前,Pinia 会将状态回滚到先前保存的状态,以确保状态不会因为更新而丢失。

  • 应用更新:Pinia 会应用收到的更新,例如添加、删除或修改状态。

  • 重新应用状态:更新应用后,Pinia 会再次应用保存的状态,以确保在更新过程中不丢失任何状态。

通过这种方式,Pinia 可以安全地在开发过程中接受并应用 HMR 更新,从而保持应用程序的状态与代码的一致性,而不需要刷新整个页面。

使用示例如下:

const useUser = defineStore(...)
if (import.meta.hot) {
   import.meta.hot.accept(acceptHMRUpdate(useUser, import.meta.hot))
 }
<think>嗯,用户想了解Pinia的状态管理库的组成部分。首先,我需要回忆一下Pinia的基本结构和它与Vuex的关系。根据之前看到的引用,PiniaVuex的升级版,用于Vue3的状态管理。那它的组成部分应该包括Store、State、Getters、Actions这些核心部分,可能还有插件之类的。 接下来,我需要检查用户提供的引用内容,看看里面有没有关于组成部分的具体描述。引用[4]提到,defineStore的第一个参数是id,第二个参数是一个包含state、getters、actions的对象。这说明Store是由这些部分组成的。那组成部分应该包括Store定义,以及其中的state、getters、actions。 另外,引用中还提到了创建根存储和挂载到Vue实例的过程,所以安装和配置部分可能也是组成部分之一。比如使用createPinia创建实例,然后在main.js中挂载。这部分可能属于核心模块或初始化配置。 还要注意是否有插件系统,比如持久化插件,引用[1]提到了Pinia持久化插件的使用,所以插件机制可能也是Pinia的一部分。不过用户的问题是关于组成部分,可能插件属于扩展部分,不是核心组成部分,但需要确认。 另外,用户的问题可能希望了解每个部分的作用,比如state存储数据,getters计算派生状态,actions处理异步和逻辑。Store作为容器,由这些组成。 总结一下,Pinia的组成部分应该包括: 1. Store定义:使用defineStore创建,包含唯一id和配置对象。 2. State:存储响应式数据。 3. Getters:计算属性,基于state派生数据。 4. Actions:处理业务逻辑,同步或异步。 5. 插件系统:扩展Pinia功能,比如持久化。 6. 安装与配置:createPinia创建实例,挂载到Vue应用。 需要按照这些点来组织回答,同时引用提供的参考文献来支持每个部分。比如引用[4]详细说明了defineStore的参数和state、getters、actions的结构,可以引用它来说明核心部分。安装部分可以参考引用[2]和[3],说明如何创建和挂载Pinia实例。 可能还需要区分核心部分和扩展部分,比如插件不是必须的,但属于组成部分之一。确保回答结构清晰,分点列出,每个部分简要说明,并附上代码示例或引用来源。 最后,生成相关问题时要围绕组成部分可能的疑问,比如如何定义Store,State和Vuex的区别,插件如何使用等。</think>### Pinia状态管理库的组成部分 Pinia作为Vue3推荐的状态管理库,其核心设计围绕**Store(存储容器)**展开,通过模块化方式管理应用状态。以下是其主要组成部分及作用: --- #### 1. **Store(存储容器)** Store是Pinia的核心单元,每个Store对应一个独立的功能模块。通过`defineStore`方法定义,需包含**唯一ID**和**配置对象**: ```javascript import { defineStore } from 'pinia'; const useCounter = defineStore('counter', { state: () => ({ count: 0 }), getters: { doubleCount: (state) => state.count * 2 }, actions: { increment() { this.count++ } } }) ``` - **唯一ID**(如`'counter'`)用于区分不同Store,类似命名空间[^4]。 - **配置对象**包含`state`、`getters`、`actions`三个核心模块。 --- #### 2. **State(状态)** State是存储响应式数据的容器,通过函数形式初始化: ```javascript state: () => ({ count: 66 }) ``` - 数据可直接通过`store.count`访问,或通过`store.$state`获取完整状态树[^4]。 --- #### 3. **Getters(计算属性)** Getters用于派生状态,类似Vue的计算属性: ```javascript getters: { doubleCount: (state) => state.count * 2 } ``` - 支持通过`this`访问其他Getter,但需显式定义返回值类型[^4]。 --- #### 4. **Actions(操作)** Actions处理业务逻辑,支持同步和异步操作: ```javascript actions: { async fetchData() { const res = await api.getData(); this.count = res.data; } } ``` - 可通过`this`直接修改State,无需像Vuex一样提交Mutation[^4]。 --- #### 5. **插件系统** Pinia支持通过插件扩展功能,例如**持久化存储**: ```javascript import { createPinia } from 'pinia'; import piniaPluginPersist from 'pinia-plugin-persist'; const pinia = createPinia(); pinia.use(piniaPluginPersist); ``` - 插件可拦截Store的初始化过程,增强状态管理能力[^1]。 --- #### 6. **安装与配置** 需在Vue应用中挂载Pinia实例: ```javascript // main.js import { createApp } from 'vue'; import { createPinia } from 'pinia'; const app = createApp(App); app.use(createPinia()); app.mount('#app'); ``` - 通过`createPinia()`创建根存储,确保所有组件可访问Store[^2][^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jinuss

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值