
- Published on
- /
42 mins read
/––– views
Share:
一、 🏞️创建源码分析环境
1. 创建一个 vue 项目
2. Pinia 源码入口
🚗源码地址: github.com/vuejs/pinia
📦打包文件: rollup.config.js
🚪入口文件: packages/pinia/src/index.ts
3. 复制 Pinia 源码 & 在 main.ts 中初始化 Pinia 插件
将 pinia/packages/pinia/src 目录下的所有文件复制到我们之前生成项目的/src/pinia 中。
在 main.ts 中安装初始化 Pinia:

4. 添加必要仓库依赖
此时通过 yarn dev 启动项目时,会报缺少依赖。
在 rollup.config.js 第122行,可以看到依赖分别有
vue-demi:一个可以帮助我们开发在vue2、vue3上通用的 vue 库的开发工具。vue:vue 项目。@vue/composition-api:vue 组合式 api 插件。
在我们的项目中安装好这两个库(vue 在创建项目时已经安装了)。
5. 添加必要环境变量
此时通过 yarn dev 启动项目时,会报饮用错误。
环境变量位于 rollup.config.js 第167行:
我们在 vite.config.ts 中添加缺少的环境变量:
6. 环境测试
在 src/pinia/createPinia.ts 中输出字符串,查看控制台是否正常打印,如正常打印则源码分析环境正常运行:

二、🧐源码分析(1)——执行流程
在 defineStore、 createPinia、 useStore 等关键函数内打印日志来确定执行顺序:

可以确定执行顺序为:
其中 defineStore() 是在引用阶段被调用,并返回 useStore() 函数,之后便开始 Vue 的流程。注册插件等,最后在页面内调用 useStore(),创建 Store 等步骤。
三、🧐源码分析(2)——createPinia
使用 Pinia 前需要在 vue 中初始化一个注册 Pinia,注册的方法是使用 :
显然 createPinia 函数返回的是一个 vue插件 。通过插件的方式安装到 vue 中。
1. 源码目录
在 src/pinia/index.ts 目录中可以找到:
显然 createPinia 函数的源码目录为: src/pinia/createPinia.ts 。
2. createPinia
在函数的最开始,通过 effectScope 声明了一个 ref,并赋值给了state,我们将其 简单理解为声明了一个 ref 并赋值给 state 。
四、🧐源码分析(3)——defineStore
1. 三种创建方式
defineStore 所在位置: src/pinia/store.ts 进入文件之后可以看到通过函数重载的方式提供给我们三种参数传递方式:

其中参数含义如下:
id:store 的 id,必须唯一。options: 与 Vue 的选项式 API 类似,我们也可以传入一个带有id、state、actions与getters属性的 Option 对象。storeSetup:以setup的方式创建,与 Vue 的 setup 函数 相似。在 storeSetup 中:ref()等同于state。computed()等同于getters。function()等同于actions。
2. defineStore 的执行逻辑
在 defineStore 函数中,并没有很特别的逻辑,首先是对三种创建方式进行兼容,然后定义一个名为 useStore 的函数,然后返回 useStore。
useStore 具体做了什么下节分析。
虽然我们前面定义了一个 store,但在我们使用 <script setup> 调用 useStore() (或者使用 setup() 函数,像所有的组件那样) 之前,store 实例是不会被创建的。
4. useStore
在之前我们分析了 defineStore 方法调用的时候返回了 useStore 方法,接下来看一下此方法究竟干了些什么。
从上边代码中,可以发现最关键的两个函数是 createSetupStore、 createOptionsStore,分别是创建 组合式Store 和 选项式Store。里边包含了创建 store 的关键逻辑,下面分别来看一下。
5. createSetupStore
createSetupStore 的作用是创建一个组合式的 store,之后的 createOptionsStore 其实也是把 option 转化后调用 createSetupStore 来创建 store。createSetupStore 的源码很长,我们分批研究。对于一些变量的定义等内容在此省略,只关注最核心逻辑,详细的注释可以查看 Github 中的源码。
(1) 参数
createSetupStore 总共接收了6个参数:
- $id :当前 Store 的 ID,
- setup defineStore 或者 createOptionsStore 传入的 setup 函数
- options 配置选项,state、getter、actions 等
- pinia Pinia 实例
- hot 热更新相关
- isOptionsStore 是否是 选项式 Store 创建
(2) 创建 Store
此过程中,创建一个 setupStore 常量,创建了一个作用域并执行了 setup 函数,获取到 setup 函数中返回的内容,也就是我们定义的 state、getter、action 等内容。
在此过程中,state 的内容也会被存储到 pinia.state 中。action 则会被 wrapAction 处理。
对每一项 action 进行处理,目的是为了支持 $onAction 方法,此方法会在执行 action 时执行回调函数,回调函数可以接收三个参数分别是:被调用的 store、action 的名字、传递给 action 的参数。在 store 中还会有一些基础操作的 API ,请看下节。
(3) 基础 API
在 Pinia 的 store 中存在很多基础 API,比如:获取 store id $id、增加 action 调用回调 $onAction()、重置 store $reset()、变更 store $patch()、订阅 $subscribe()、移除 store $dispose、获取所有 state $state 等。我们逐个分析。

基础的 API 首先被储存在 partialStore 中,然后创建一个 store 常量,并且把这些基础 API 和 store 的内容都合并到 store 常量中。
(4) Store 和 基础 API 合并
在 (2) 和 (3) 中我们创建了 store 的基本内容和基础的API,现在新建一个变量,并把它们合并到一块:
现在,还缺少一个获取所有 state 得属性: $state ,我们使用 defineProperty 给 store 增加 $state 属性 :
(5) 对于 Pinia 自定义插件的处理
在之前的 createPinia() 方法中,Pinia 实例上存在一个 use() 方法是对自定义插件的支持,在这里我们需要对安装的插件进行处理,调用左右的插件函数,并给函数传入 store app piain options 四个参数。
我们可以这样给 Piain 安装插件:
使用插件:

6. createOptionsStore
createOptionsStore 的代码量比较少,从下面的代码可以发现,基本的逻辑就是从 options 中获取到 state、 actions、 getters,定义一个 setup 函数并调用 createSetupStore 创建 Store,还要将 getters 转换为 computed。
五、🧐源码分析(4)—— store 的基础 API 实现
1. $id
这个没啥好说的,就是 createSetupStore 参数中的 $id。
2. $onAction
设置一个回调,当一个 action 即将被调用时,就会被调用。 回调接收一个对象, 其包含被调用 action 的所有相关信息:
store: 被调用的 storename: action 的名称args: 传递给 action 的参数
除此之外,它会接收两个函数, 允许在 action 完成或失败时执行的回调。
它还会返回一个用来删除回调的函数。 请注意,当在组件内调用 store.$onAction() 时,除非 detached 被设置为 true, 否则当组件被卸载时,它将被自动清理掉。
在 Pinia 的源码中,关于 $onAction 的代码是这样的:
可以发现, $onAction 就是给 addSubscription 函数绑定了个 null 的 this 和一个参数,再来看一下这个 addSubscription 是何方神圣:
3. $patch
将一个 state 补丁应用于当前状态。允许传递嵌套值。
$patch 允许两种参数传递方式,传入一个函数,或一个 state 的补丁。
其中 triggerSubscriptions 方法是发布者,执行订阅函数的回调:
4. $reset
通过建立一个新的状态对象,将 store 重设为初始状态。
5. $subscribe
设置一个回调,当状态发生变化时被调用。它会返回一个用来移除此回调的函数。 请注意,当在组件内调用 store.$subscribe() 时,除非 detached 被设置为 true, 否则当组件被卸载时,它将被自动清理掉。
其中, addSubscription 函数可以查看2. $onAction 。
6. $dispose
停止 store 的相关作用域,并从 store 注册表中删除它。 插件可以覆盖此方法来清理已添加的任何副作用函数。 例如, devtools 插件停止显示来自 devtools 的已停止的 store。
六、🧐源码分析(5)—— 辅助函数
Pinia 也提供了一组类似 Vuex 的 映射 state 的辅助函数。你可以用和之前一样的方式来定义 Store。这里不做使用的介绍,用法请看官网:https://pinia.vuejs.org/zh/introduction.html
🚪源码目录: src/pinia/mapHelpers.ts
1. mapActions
mapActions 有两种传参方式,两种传参方式第一个参数都是 defineStore 中返回的 useStore 函数。
- 传入一个对象,key 为映射到
methods中的名字,value 为 action 的名字。 - 传入一个数组,item 为 action 的名字。
下面是 mapActions 的源码,主要思想就是调用 useStore 方法得到 Store ,然后取出需要的 action 并返回。
2. mapStores
通过生成一个对象,传递到组件的 computed 字段 以允许在不使用组合式 API(setup()) 的情况下使用 store。 它接受一个 store 定义的列表参数。
3. mapState
通过生成一个对象,并传递至组件的 computed 字段, 以允许在不使用组合式 API(setup())的情况下使用一个 store 的 state 和 getter。 该对象的值是 state 属性/getter, 而键是生成的计算属性名称。 你也可以选择传递一个自定义函数,该函数将接收 store 作为其第一个参数。 注意,虽然它可以通过 this 访问组件实例,但它没有标注类型。
4. mapGetters
mapGetters 已废弃,直接使用 mapState 即可。
5. mapWritableState
在使用 $mapState 把 state 导入 computed 时,如果直接去修改 state 的值是不允许的。

$mapWritableState ****除了创建的计算属性的 setter,其他与 mapState() 相同, 所以 state 可以被修改。 与 mapState() 不同的是,只有 state 属性可以被添加。
✨结语
代码虽然比较多,但核心逻辑还是借助 vue 的 ref 和 reactive 实现响应式。把 state 处理为 ref ,把 getters 处理成 computed ,提供一些基础方法,并使用单例模式返回一个实例。
完整注释代码: