Pinia源码分析【2】- createPinia

1,233 阅读4分钟

专栏导航

分析pinia源码之前必须知道的API

Pinia源码分析【1】- 源码分析环境搭建

Pinia源码分析【2】- createPinia

pinia源码分析【3】- defineStore

pinia源码分析【4】- Pinia Methods

前言

参考源码pinia V2.0.14

源码分析仓库:github.com/vkcyan/mini…

上一篇文章我们主要介绍了如何搭建一个pinia源码阅读环境;本文主要介绍pinia在vue3初始化阶段相关逻辑,以及如何构建pinia对象。

正文

根据官方文档,我们使用pinia首先需要是将他注册到vue

const pinia = createPinia();
app.use(pinia);

createPinia的阶段究竟做了什么,他又是如何被注册到vue中呢?我们要从createPinia中寻找答案。

源码地址

我们通过pinia\src\index.ts找到

export { createPinia } from './createPinia' // `pinia\src\createPinia.ts为源码文件`

createPinia函数

在函数的最开头,我们就可以看到通过effectScope声明了一个ref,并赋值给了state,这里的effectScope是高级API,未来会单独介绍,有兴趣的同学可以看一下官方文档,我们将其简单理解为声明了一个ref并赋值给state

export function createPinia(): Pinia {
    const scope = effectScope(true);
    const state = scope.run<Ref<Record<string, StateTree>>>(() =>
       ref<Record<string, StateTree>>({})
    )!;
    // 简化理解
    // const state = ref({})
    
    // ...
}
​

pinia通过markRaw进行包装,将其标记为不会转化为响应式,最终pinia对象被createPinia函数返回,执行vue.use(pinia)的时候便会执行pinia对象中的install函数。

export function createPinia(): Pinia {
  // ...
  let _p: Pinia["_p"] = []; // 所有需要安装的插件
  let toBeInstalled: PiniaPlugin[] = []; // install之前保存的待安装插件
​
  // 使用markRaw标记pinia使其不会被响应式
  const pinia: Pinia = markRaw({
    // vue.use实际执行逻辑
    install(app: App) {
      setActivePinia(pinia); // 设置当前使用的 pinia
      if (!isVue2) { // 如果是vue2,全局注册已经在PiniaVuePlugin完成,所以这段逻辑将跳过
        pinia._a = app; // 保存app实例
        app.provide(piniaSymbol, pinia); // 通过provide传递pinia实例,提供给后续使用
        app.config.globalProperties.$pinia = pinia; // 设置全局属性 $pinia
        toBeInstalled.forEach((plugin) => _p.push(plugin)); // 处理未执行插件
        toBeInstalled = [];
      }
    },
    use(plugin) {
      if (!this._a && !isVue2) { // 如果use阶段为初始化完成则暂存toBeInstalled中
        toBeInstalled.push(plugin);
      } else {
        _p.push(plugin);
      }
      return this;
    },
    _p, // 所有pinia的插件
    _a: null, // app实例,在install的时候会被设置
    _e: scope, // pinia的作用域对象,每个store都是单独的scope
    _s: new Map<string, StoreGeneric>(),  // store缓存 key为pinia的id value为pinia的对外暴露数据
    state, // pinia所有state的合集 key为pinia的id value为store下的所有state(所有可访问变量)
  });
  return pinia;
}

返回值的含义以及作用

image-20220713153012540

初始化的逻辑相对比较简单,只需要了解effectScope markRaw便能完全读懂,install阶段组成的pinia对象被setActivePinia保存了下来,而这个对象贯穿pinia整个生命周期,每个字段的作用在后面的源码解读中都会有所体现。

关于Vue2

通过pinia官网,我们可以了解到pinia支持vue2,不过vue2环境需要在使用createPinia之前,预先安装插件PiniaVuePlugin,通过pinia的入口文件了解到PiniaVuePlugin的源码入口为pinia\src\vue2-plugin.ts

PiniaVuePluginvue2插件比较主流的实现方式,获取Vue实例,通过mixin实现数据共享。如果了解过vuex的源码,相信对以下代码会十分熟悉。

export const PiniaVuePlugin: Plugin = function (_Vue) {
    // Equivalent of
    // app.config.globalProperties.$pinia = pinia
    // pinia在vue2中的注册逻辑与vuex核心逻辑几乎一致,
    // 注入全局mixin的beforeCreate
    _Vue.mixin({
        beforeCreate() {
            const options = this.$options;
            // 在根节点通过vue.use中注册了pinia
            if (options.pinia) {
                const pinia = options.pinia as Pinia;
                // defineProperty版provided实现
                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;
                }
​
                // 保存Vue实例
                pinia._a = this as any;
                if (IS_CLIENT) {
                    setActivePinia(pinia);
                }
            } else if (!this.$pinia && options.parent && options.parent.$pinia) {
                // 所有子组件/页面都继承上一层的pinia
                this.$pinia = options.parent.$pinia;
            }
        },
        destroyed() {
            delete this._pStores;
        },
    });
};

关于devTool

createPinia中存在这样一段代码

if (__DEV__ && IS_CLIENT && !__TEST__) {
    pinia.use(devtoolsPlugin);
}

如果是开发环境,并且是浏览器环境,并且不是测试环境,就会向pinia注册devtoolsPlugin,也就是将pinia注册到浏览器插件Vue.js devtools中。

image-20220713170645056

结语

createPinia的源码解读就全部结束了,现在我们已经了解初始化的具体流程,以及生成的pinia对象中存在什么参数,这些参数在运行阶段都会发挥它应用的价值。

下一章我们将要解析创建以及使用pinia的相关源码,defindStore函数实现逻辑,在defindStore中我们将会了解到install阶段每个字段的实际用途,以及pinia的核心响应原理。

我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿