likes
comments
collection
share

Pinia 原理解读 - 引入与初始化数据仓库流程

作者站长头像
站长
· 阅读数 58

一、前置知识与入门

1.1 核心概念

Pinia 的特性:

  • pinia 也具有 state、getters、actions,但是移除了 modules、mutations ;

  • pinia 的 actions 里面可以支持同步也可以支持异步;

  • pinia 采用模块式管理,每个 store 都是独立的,互相不影响;

Pinia 与 Vuex 相比主要功能优点在于:

  • 兼容支持 Vue 2.x 与 3.x 项目;
  • 更友好的 Typescript 语法支持;
  • 比 Vuex 体积更小,构建压缩后只有 1KB 左右的大小;
  • 支持服务端渲染;

1.2 基本使用

Vue 应用注册引入 Pinia

// vue-project/src/main.ts

import { createSSRApp } from 'vue';
import * as store from 'pinia';

import App from './App.vue';

export function createApp() {
  const app = createSSRApp(App);

  app.use(store.createPinia());

  return {
    app,
    store,
  };
}

创建仓库模块

// /src/stores/user.ts

import { defineStore } from 'pinia';

export const useUserStore = defineStore('user', {
  state: () => ({
    id: '',
    avatar: '',
    nickname: '',
  }),

  actions: {
    setId(id = '') {
      this.id = id
    },
  },
});

仓库数据读取与设置

import { storeToRefs } from 'pinia';
import { useUserStore } from '@/stores/user';

const userStore = useUserStore();
const { id, info: userInfo } = storeToRefs(userStore);

// 批量修改数据
const patchStore = () => {
  userStore.$patch({
    avatar: 'xxxx',
    nickname: "JesBrian",
  });
};
// 调用 actions 方法
const setUserId = (id: string = '') => {
  userStore.setId(id);
};
// 重置 store 数据为初始值
const reset = () => {
  userStore.$reset();
};


二、源码原理的分析解读

2.1 createPinia

在上面基本使用的案例当中我们知道createPinia方法是 Pinia 提供出来创建 Pinia 对象并且通过 vue.use 注册引入到 Vue 项目当中。

createPinia 概览

// vuejs:pinia/packages/pinia/src/createPinia.ts

export function createPinia(): Pinia {
  const scope = effectScope(true)
  const state = scope.run<Ref<Record<string, StateTree>>>(() =>
    ref<Record<string, StateTree>>({})
  )!

  // ··· ···
}

creatPinia函数的最开始地方我们能看到,通过effectScope声明了一个ref的响应式数据,并赋值给了state变量,这里的将其简单理解为声明了一个ref并赋值给state

  • effectScope:这是一个 Vue 3.x 高阶的响应式的 api,能够对这个 effect 里面的响应式副作用(计算属性、监听器)统一进行操作处理,例如调用stop停止监听拦截等,具体可以查看文档:cn.vuejs.org/api/reactiv…

// vuejs:pinia/packages/pinia/src/createPinia.ts

export function createPinia(): Pinia {
  // ··· ···

  // 定义 pinia 插件相关的变量
  let _p: Pinia["_p"] = [];
  let toBeInstalled: PiniaPlugin[] = [];

  // markRaw:标记该 pinia 不会被响应式转换和监听
  const pinia: Pinia = markRaw({
    install(app: App) { // install: vue.use实际执行逻辑
      setActivePinia(pinia); // 设置当前使用数据仓库为根 pinia 对象
      if (!isVue2) { // 如果是 vue 2.x,全局注册已经在 PiniaVuePlugin 完成,所以这段逻辑将跳过
        pinia._a = app; // 设置 vue 项目的 app 实例
        app.provide(piniaSymbol, pinia); // 通过 provide 来注入 pinia 实例
        app.config.globalProperties.$pinia = pinia; // 在 vue 项目当中设置全局属性 $pinia
        
        toBeInstalled.forEach((plugin) => _p.push(plugin)); // 处理安装未执行的 pinia 插件
        toBeInstalled = []; // 清空未安装的插件列表
      }
    },
    
    use(plugin) { // pinia 使用插件时候调用执行,将 pinia 插件都先塞到一个 _p 的数组当中,后续再进行初始化执行
      if (!this._a && !isVue2) {
        toBeInstalled.push(plugin);
      } else {
        _p.push(plugin);
      }
      return this;
    },
    
    _p, // pinia 的插件
    _a: null, // Vue 项目的 app 实例,在 install 执行时设置
    _e: scope, // pinia 的 effect 作用域对象,每个store都是单独的scope
    _s: new Map<string, StoreGeneric>(),  // 以 Map 的数据结构形式存储 pinia 数据仓库 store,类似 state
    state, // pinia 数据仓库 state 合集
  });
  return pinia;
}

接着继续分析下面的逻辑能看到方法内通过markRaw创建了一个包含 install、use、响应式数据 state 属性的 pinia 对象;并且最终将 pinia 对象作为createPinia函数的返回值。

  • 这个 pinia 对象经过markRaw的包装会被 Vue 标记为不会转化为响应式;能够节约内存的使用,提高运行效率。

Pinia 对象的属性

上面我们通过对createPinia方法的分析已经了解到了 pinia 对象的创建的大概流程,这里我们简单分析描述下这个 pinia 对象上面的属性:

  • install:方法是提供在 Vue 项目当中执行app.use(pinia)时候执行的;
    • 调用执行时会保存当前的 pinia 对象,并且将该 pinia 对象注入到当前的 Vue 项目的 vue 实例的全局变量当中;
  • use:是提供给 pinia 插件安装使用的方法,执行pinia.use(devtoolsPlugin)时候调用就是这个方法;
    • 将插件注册到插件列表变量当中;
  • _p:记录插件集合
  • _a:当前的 vue 实例
  • _e:store 数据仓库的 effect 作用域
  • _s:以 Map 的数据结构形式存储 pinia 数据仓库 store
  • state:指向 pinia 数据仓库 store 的数据
    • key为pinia的id value为store下的所有state(所有可访问变量)

与 Vue 2.x 版本的关联

Vue 2.x 使用 Pinia

查看 Pinia 的文档,Pinia 也是支持 Vue 2.x 版本的,但是在 Vue 2.x 环境当中需要在使用 createPinia 之前安装PiniaVuePlugin插件。

Pinia 原理解读 - 引入与初始化数据仓库流程

import { createPinia, PiniaVuePlugin } from 'pinia'

Vue.use(PiniaVuePlugin) // 在 Vue 2.x 的项目当中需要额外执行,在 Vue 3.x 当中不需要
const pinia = createPinia()

new Vue({
  el: '#app',
  // 其他选项...
  // ...
  // 注意同一个 `pinia` 实例可以在多个 Vue 应用程序中使用
  // 同一个页面
  pinia,
})

PiniaVuePlugin 简析

// vuejs:pinia/packages/pinia/src/vue2-plugin.ts

export const PiniaVuePlugin: Plugin = function (_Vue) {
  _Vue.mixin({
    beforeCreate() {
      const options = this.$options
      if (options.pinia) {
        const pinia = options.pinia as Pinia
        if (!(this as any)._provided) {
          const provideCache = {}
          Object.defineProperty(this, '_provided', {
            get: () => provideCache,
            set: (v) => Object.assign(provideCache, v),
          })
        }
        ;(this as any)._provided[piniaSymbol as any] = pinia

        if (!this.$pinia) {
          this.$pinia = pinia
        }

        pinia._a = this as any
        if (IS_CLIENT) {
          setActivePinia(pinia)
        }
      } else if (!this.$pinia && options.parent && options.parent.$pinia) {
        this.$pinia = options.parent.$pinia
      }
    },
    destroyed() {
      delete this._pStores
    },
  })
}

通过阅读上述 PiniaVuePlugin 的源码,如果了解过 Vuex 的实现原理的话能够发现两者是类似的实现原理,通过 Vue 框架提供的 mixin 的能力来对 this 对象的 $pinia 属性注入,从而实现全局数据仓库的的一个共享访问

  • 在 beforeCreate 创建组件实例前阶段进行 $pinia 属性的挂载;

  • 在 destoryed 销毁组件实例阶段对当前组件实例卸载删除数据连接;

小结:

至此 Pinia 的注册流程已经解析完了,在这个章节当中我们主要对 Vue 项目当中引入 Pinia 的流程和原理进行了分析,接下来我们会对 Pinia 初始化创建数据仓库这个阶段进行源码解析。

2.2 defineStore

数据仓库的初始化

defineStore该方法在上面 “基础使用” 的章节当中我们能够看到是用来创建 Pinia 数据仓库模块。接下来这个章节我们便要来对该方法进行原理性的解剖分析。

三种参数形式的初始化调用

import { ref } from 'vue';
import { defineStore } from 'pinia';

export const useUserStore = defineStore('user', {
  state: () => ({
    name: '',
    age: 0,
  }),
  getters: {
    description() {
      return `${this.name} - ${this.age}`;
    },
  },
  actions: {
    setName(name = '') {
      return this.name = name;
    },
    setAge(age = 10) {
      return this.age = age;
    },
  },
});

export const useAdminStore = defineStore({
  id: 'admin',
  state: () => ({
    name: '',
    level: 0,
  }),
  getters: {
    description(): string {
      return `${this.name} - ${this.level}`;
    },
  },
  actions: {
    setName(name = '') {
      return this.name = name;
    },
    setAge(age = 10) {
      return this.age = age;
    },
  },
});

export const useAuthStore = defineStore('auth', () => {
  const bit = ref(0);
  const checkPermission = () => {
    // ··· ···
  }
  return {
    bit, checkPermission
  }
});

在上述例子当中能够看到defineStore有传入三种的形式的参数的调用方式:

  • id:定义 store 的唯一 id,单独传参或者通过 options.id 进行声明
  • options:具体配置信息,是对象可以传 state,getters,action,id 属性例如上面例子中的第一、二种声明方式;如果第二个参数传入的是 Function,则自主声明变量方法,例如例子中的第三种声明方式;
  • storeSetup:仅限第三种 store 的声明方式,传入一个函数,函数的返回值作为数据仓库的 state 与 actions

查看defineStore具体的源码发现是利用 TypeScript 函数重载来实现传递不同参数进行数据仓库的初始化处理。

  • 上述的例子当中对应下面源码当中 TS 定义的三种 defineStore 重载形式
// vuejs:pinia/packages/pinia/src/store.ts

export function defineStore<
  Id extends string,
  S extends StateTree = {},
  G extends _GettersTree<S> = {},
  // cannot extends ActionsTree because we loose the typings
  A /* extends ActionsTree */ = {}
>(
  id: Id,
  options: Omit<DefineStoreOptions<Id, S, G, A>, 'id'>
): StoreDefinition<Id, S, G, A>

export function defineStore<
  Id extends string,
  S extends StateTree = {},
  G extends _GettersTree<S> = {},
  // cannot extends ActionsTree because we loose the typings
  A /* extends ActionsTree */ = {}
>(options: DefineStoreOptions<Id, S, G, A>): StoreDefinition<Id, S, G, A>

export function defineStore<Id extends string, SS>(
  id: Id,
  storeSetup: () => SS,
  options?: DefineSetupStoreOptions<
    Id,
    _ExtractStateFromSetupStore<SS>,
    _ExtractGettersFromSetupStore<SS>,
    _ExtractActionsFromSetupStore<SS>
  >
): StoreDefinition<
  Id,
  _ExtractStateFromSetupStore<SS>,
  _ExtractGettersFromSetupStore<SS>,
  _ExtractActionsFromSetupStore<SS>
>

初始化仓库具体逻辑

// vuejs:pinia/packages/pinia/src/store.ts

export function defineStore(
  idOrOptions: any,
  setup?: any,
  setupOptions?: any
): StoreDefinition {
  let id: string
  let options:
    | DefineStoreOptions<
        string,
        StateTree,
        _GettersTree<StateTree>,
        _ActionsTree
      >
    | DefineSetupStoreOptions<
        string,
        StateTree,
        _GettersTree<StateTree>,
        _ActionsTree
      >

  // 判断第二个参数是否是函数
  const isSetupStore = typeof setup === 'function'
  
  // 兼容处理三种不同参数重载的情况,从三种形式参数当中抽取出 id 与 options 参数;
  //   id 后续会作为当前该 store 的模块标识 id
  if (typeof idOrOptions === 'string') {
    id = idOrOptions
    options = isSetupStore ? setupOptions : setup
  } else {
    options = idOrOptions
    id = idOrOptions.id
  }

  // 声明 useStore 函数并且作为 defineStore 函数的返回值
  function useStore(pinia?: Pinia | null, hot?: StoreGeneric): StoreGeneric {
    // ··· ···
  }

  useStore.$id = id

  return useStore
}

通过对defineStore的源码大致逻辑流程分析可以得知,defineStore里面含有一个useStore方法,并且defineStore函数的返回值为该useStore函数。因此useStore才是 Pinia store 数据仓库的核心创建逻辑,接下我们重点分析其函数逻辑。

useStore 解析

// vuejs:pinia/packages/pinia/src/store.ts

export function defineStore(
  idOrOptions: any,
  setup?: any,
  setupOptions?: any
): StoreDefinition {
	// ··· ···
  
  function useStore(pinia?: Pinia | null, hot?: StoreGeneric): StoreGeneric {
    // 通过 getCurrentInstance 获取当前 Vue 的组件实例
    const currentInstance = getCurrentInstance()

    //
    pinia =
      (__TEST__ && activePinia && activePinia._testing ? null : pinia) ||
      (currentInstance && inject(piniaSymbol, null))

    // 设置当前 Pinia 活跃的 state 实例
    if (pinia) setActivePinia(pinia)

    // 开发模式下并且当前没有 pinia 仓库数据 state 实例情况下抛出异常
    if (__DEV__ && !activePinia) {
      throw new Error(
        `[🍍]: getActivePinia was called with no active Pinia. Did you forget to install pinia?\n` +
          `\tconst pinia = createPinia()\n` +
          `\tapp.use(pinia)\n` +
          `This will fail in production.`
      )
    }

    // 设置当前 pinia 变量值为当前活跃的 pinia state 实例
    pinia = activePinia!

    // 单例模式的应用:如果 pinia 中已经有对应 id 模块的 store 实例则直接获取该 store 实例返回,否则执行创建 store 逻辑
    if (!pinia._s.has(id)) {
      
      // 根据 defineStore 时候传入第二个参数类型区分调用创建 pinia store 数据仓库方法
      // 		若第二个参数传递的是函数则执行 createSetupStore,否则执行 createOptionsStore
      if (isSetupStore) {
        createSetupStore(id, setup, options, pinia)
      } else {
        createOptionsStore(id, options as any, pinia)
      }
    }

    const store: StoreGeneric = pinia._s.get(id)!

    // StoreGeneric cannot be casted towards Store
    return store as any
  }
  
  useStore.$id = id
  
  return useStore
}

useStore的逻辑其实非常简单,就是获取当前 id 的 pinia store 实例,这里使用单例模式进行优化:

  • 如果 pinia 中已经有对应 id 模块的 store 实例则直接获取该 store 实例返回;
    • 前面在createStore方法章节当中解析了当前 pinia 对象的 _s 属性是 Map 的数据结构对 Pinia 所有模块 store 的存储,因此此处使用Mapget(key)方法获取当前 id 对应的模块 store 实例;
  • 否则根据前面defineStore调用时候传递的参数进行区分调用createOptionsStorecreateSetupStore两个方法。

createOptionsStore

我们先来看createOptionsStore这个比较简单的方法,这个方法是调用defineStore函数时候传入 Object 对象数据结构参数的形式才会执行。

// vuejs:pinia/packages/pinia/src/store.ts

function createOptionsStore<
  Id extends string,
  S extends StateTree,
  G extends _GettersTree<S>,
  A extends _ActionsTree
>(
  id: Id,
  options: DefineStoreOptions<Id, S, G, A>,
  pinia: Pinia,
  hot?: boolean
): Store<Id, S, G, A> {
  const { state, actions, getters } = options

  // 判断能否从当前 pinia 已经实例化的 store 数据仓库集 state 当中获取当前 ID 的数据仓库
  const initialState: StateTree | undefined = pinia.state.value[id]

  let store: Store<Id, S, G, A>

  function setup() {
    // 如果没有初始化过当前 ID 的 state 则使用 options 的 state 方法创建一个响应式的数据
    if (!initialState) {
      pinia.state.value[id] = state ? state() : {}
    }

    // 通过 toRefs 获取一个解构仍能保持响应式的当前 ID 的 state 数据仓库
    const localState = toRefs(pinia.state.value[id])

    return assign(
      localState,
      actions,
      Object.keys(getters || {}).reduce((computedGetters, name) => {
        // 判断如果 getter 的 key 已经在 state 当中则进行 warn 提示处理
        if (__DEV__ && name in localState) {
          console.warn(
            `[🍍]: A getter cannot have the same name as another state property. Rename one of them. Found with "${name}" in store "${id}".`
          )
        }

        // 使用 markRaw 标记对象,防止对象被 Proxy 劫持成为响应式数据
        computedGetters[name] = markRaw(
          // 使用计算属性处理 options 的 getters -- 也是因为这步操作使得 pinia 的 getters 拥有 Vue.js 的 computed 的能力
          computed(() => {
            setActivePinia(pinia)

            const store = pinia._s.get(id)!

            // 通过 call 处理 this 指向
            return getters![name].call(store, store)
          })
        )
        return computedGetters
      }, {} as Record<string, ComputedRef>)
    )
  }

  // 创建和整理好各个参数后调用 createSetupStore 方法
  store = createSetupStore(id, setup, options, pinia, hot, true)

  // 给 pinia store 设置 $reset 方法
  store.$reset = function $reset() {
    const newState = state ? state() : {}

    this.$patch(($state) => {
      assign($state, newState)
    })
  }

  return store as any
}

可以看到其实createOptionStore方法内部最主要还是根据 options 对象里面的数据,在方法内部构建并且封装为setup函数,setup 函数当中主要是将 options 参数中的stategetters属性分别使用toRefscomputed封装转化为 Vue.js 的ref响应式数据与computed计算属性并返回;

  • state 会先判断是否已经有实例化响应式的 state 数据,若没有则调用 options 的 state 方法进行响应式数据实例;
  • 接着将stategetters属性分别使用toRefscomputed封装转化为 Vue.js 的ref响应式数据与computed计算属性作为 setup 函数的返回值对象属性处理;
    • 也是因为这步操作使得 pinia 数据仓库的 state 的里面的属性具有响应式能力及 getters 具有计算属性的能力;
  • options 中的 actions 属性保持原样作为 setup 函数返回的对象属性,后续会在createSetupStore内进行进一步处理;
    • 这里留意createSetupStore方法有一个isOptionsStore参数,标记是否是以 options 的形式进行创建数据模块,这里通过createOptionStore调用的createSetupStore则会传递 true 值。

创建好setup函数后接着便是调用createSetupStore传入 id、刚封装的 setup 方法等参数,因此其实无论以哪种参数形式调用defineStore方法最终走的都是createSetupStore方法对 pinia store 进行创建初始化处理。

createSetupStore

接着我们继续看createSetupStore这方法,从前面的分析可以看到其实那三种调用deineStore定义 pinia store 的不同参数方法最终都是归并参数成setup方法然后调用createSetupStore方法进行创建数据仓库。

// vuejs:pinia/packages/pinia/src/store.ts

function createSetupStore<
    Id extends string,
    SS extends Record<any, unknown>,
    S extends StateTree,
    G extends Record<string, _Method>,
    A extends _ActionsTree
    >(
    $id: Id,
    setup: () => SS,
    options:
        | DefineSetupStoreOptions<Id, S, G, A>
        | DefineStoreOptions<Id, S, G, A> = {},
    pinia: Pinia,
    hot?: boolean,
    isOptionsStore?: boolean
): Store<Id, S, G, A> {
  let scope!: EffectScope

  const setupStore = pinia._e.run(() => {
    scope = effectScope()
    return scope.run(() => setup())
  })!
  
  // ··· ···
}

可以看到createSetupStore方法当中会将前面统一封装处理的setup方法包裹在一个effectScope当中运行,结合着前面definedStore三种形式的传参以及createOptionStoresetup方法的定义封装,我们能够知道setup函数运行后能返回的是经过处理的响应式的 state、基于 computed 计算属性实现的 getters 以及对应的 action 方法的一个对象seupStore

对 store 的 action 方法处理增加监听后还需要对当前状态库模块 store 进行一些操作 api 的挂载,例如 reset,reset,resetpatch 等方法。下面的源码片段当中能看到这里定义了$patch$reset$dispose$subscribe$onAction等相关方法,然后使用Object.assign进行两个对象之间的属合并,从而将相关的 api 进行挂载到当前模块的 store 上。

  • 不过因为篇幅的原因,这里暂时先不展开分析里面各个 api 的具体逻辑实现了,后续的系列文章会继续重拾这块进行详细的分析。
// vuejs:pinia/packages/pinia/src/store.ts

const { assign } = Object

function createSetupStore<
    Id extends string,
    SS extends Record<any, unknown>,
    S extends StateTree,
    G extends Record<string, _Method>,
    A extends _ActionsTree
    >(
    $id: Id,
    setup: () => SS,
    options:
        | DefineSetupStoreOptions<Id, S, G, A>
        | DefineStoreOptions<Id, S, G, A> = {},
    pinia: Pinia,
    hot?: boolean,
    isOptionsStore?: boolean
): Store<Id, S, G, A> {
  let scope!: EffectScope

  function $patch(stateMutation: (state: UnwrapRef<S>) => void): void
  function $patch(partialState: _DeepPartial<UnwrapRef<S>>): void
  function $patch(
      partialStateOrMutator:
          | _DeepPartial<UnwrapRef<S>>
          | ((state: UnwrapRef<S>) => void)
  ): void {
    // ··· ···
  }
  const $reset = __DEV__ ? () => {
    // ··· ···
  } : noop
  function $dispose() {
    // ······
  }

  const partialStore = {
    _p: pinia,
    // _s: scope,
    $id,
    $onAction: //··· ···,
    $patch,
    $reset,
    $subscribe(callback, options = {}) {
      // ··· ···
    },
    $dispose,
  } as _StoreWithState<Id, S, G, A>

  const store: Store<Id, S, G, A> = reactive(partialStore) as unknown as Store<Id, S, G, A>

  // TODO: idea create skipSerialize that marks properties as non serializable and they are skipped
  const setupStore = pinia._e.run(() => {
    scope = effectScope()
    return scope.run(() => setup())
  })!

  assign(store, setupStore)

  return store
}

在对 store 添加完 api 后便是初始化 pinia 的插件了,在前面createPinia的章节当中,我们分析了 pinia 的实例对象当中存在一个_p属性,里面是 pinia 的插件合集,在defineStore-createSetupStore的时候会将当前的 store 传递给 pinia 的插件列表,一个个插件项进行直接调用实现插件的初始化处理。

// vuejs:pinia/packages/pinia/src/store.ts

function createSetupStore<
    Id extends string,
    SS extends Record<any, unknown>,
    S extends StateTree,
    G extends Record<string, _Method>,
    A extends _ActionsTree
    >(
    $id: Id,
    setup: () => SS,
    options:
        | DefineSetupStoreOptions<Id, S, G, A>
        | DefineStoreOptions<Id, S, G, A> = {},
    pinia: Pinia,
    hot?: boolean,
    isOptionsStore?: boolean
): Store<Id, S, G, A> {  
  // ··· ···

  const optionsForPlugin: DefineStoreOptionsInPlugin<Id, S, G, A> = assign(
      { actions: {} as A },
      options
  )

  // 遍历 pinia 插件列表
  pinia._p.forEach((extender) => {
    assign(
        store,
        scope.run(() =>
            // 调用 pinia 插件的 extender 方法进行安装
            extender({
              store,
              app: pinia._a,
              pinia,
              options: optionsForPlugin,
            })
        )!
    )
  })
  
  return store
}

处理完 pinia 的插件在当前 store 数据仓库当中的初始化逻辑后,createSetupStore方法也基本走完全部流程了,后面就是将这个 store 对象作为createSetupStore方法的返回值,也就是执行了defineStore函数后能够获取到的返回值即为该模块的数据仓库 store 对象。

小结:

通过本章节对defineStore这个创建 Pinia state 数据仓库的方法进行源码阅读与分析,我们了解到了 Pinia 是如何通过defineStore方法进行创建和初始化一个 store 数据仓库模块的,并且了解学习了单例模式在源码实例化当中的优化使用的例子;以及一个 store 数据仓库里面的 state、getters、action 这三个属性里面是分别如何实现的,并且对初始化创建的模块 store 进行 api 的挂载处理以及 pinia 插件的初始化加载。

Pinia 原理解读 - 引入与初始化数据仓库流程


三、初始化流程总结

至此,我们基本对 Pinia 数据仓库的初始化流程了解和学习了一遍,这里简单的进行一个总结:

  • 通过createPinia创建 pinia 实例,并且在app.use时候执行 pinia 内部的install方法;

    • install方法当中会使用 Vue.js 的provide将当前 pinia 实例注入到每一个 Vue 组件实例当中;
  • 在业务当中使用useXxx时候其实调用的是defineStore方法,该defineStore方法只有在真正调用时候才会初始化创建对应模块的数据仓库;defineStore方法的内部处理逻辑:

    • 创建一个 store 对象,将 options 的各个属性 state、getters 利用 Vue.js 的响应式 Composition API 进行处理和转换,使之成为响应式的数据后挂载在这个 store 对象上;
    • a
    • 通过Object.assign方法对这个 store 对象进行一些 api (onAction、onAction、onActionreset、$patch 等)的扩展挂载;
    • 将 pinia 的插件在这个模块化的 store 上的初始化加载;
    • 返回这个 store 对象作为defineStore方法的返回值;
  • 至此,pinia 这个全局状态管理工具的引入和模块化数据仓库 store 的初始化流程就完成了。

Pinia 初始化的流程大致分析至此,Pinia 这个库肯定不仅仅是这些内容,像是发布订阅触发,store上面的各种操作方法还没进行源码实现分析,后续也会不断深入探究这块的内容,并且持续的输出相关的原理解析、理解心得的文章。


参考资料