深海鱼的博客 深海鱼的博客
首页
  • 《ES6 教程》
  • 《TypeScript》
  • 《Vue》
  • 《React》
  • 《Git》
  • Javascript
  • CSS
手写系列
  • React项目实战
  • Vue3项目实战
  • 服务端项目实战
  • 鸿蒙项目实战
  • 小小实践
  • Vue全家桶

    • Vue2.0
    • Vue3.0
    • VueRouter
    • Vuex
  • React

    • React
  • Axios
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

深海鱼

尽人事,知天命,知行合一。
首页
  • 《ES6 教程》
  • 《TypeScript》
  • 《Vue》
  • 《React》
  • 《Git》
  • Javascript
  • CSS
手写系列
  • React项目实战
  • Vue3项目实战
  • 服务端项目实战
  • 鸿蒙项目实战
  • 小小实践
  • Vue全家桶

    • Vue2.0
    • Vue3.0
    • VueRouter
    • Vuex
  • React

    • React
  • Axios
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 导读
  • vue2

  • vuex

    • 前言
    • 调式环境准备
    • 源码基本结构与install函数
      • 一、源码基本结构
      • 二、install函数
      • 三、总结
    • ModuleCollection与Module类
    • Store实例化
    • Store实例方法
    • 辅助函数
    • 内置插件
    • 总结与常见问题
  • vue-router

  • vue3

  • react

  • Axios

  • 源码解读
  • vuex
深海鱼
2024-06-24
目录

源码基本结构与install函数

# 一、源码基本结构

Vuex源码结构如下:

vuex
  ├── helpers.js  #辅助函数
  ├── index.esm.js #esm版本入口文件
  ├── index.js #cjs版本入口文件
  ├── mixin.js #install函数使用的mixin
  ├── module #模块相关
  │   ├── module-collection.js #模块集合
  │   └── module.js #模块
  ├── plugins #插件相关
  │   ├── devtool.js #devtool插件
  │   └── logger.js #内置的logger插件
  ├── store.js #Store类
  └── util.js #工具函数
1
2
3
4
5
6
7
8
9
10
11
12
13
  • helpers.js:辅助函数的定义,如mapState、mapGetters等辅助函数都是定义在该文件内
  • index.esm.js:esm版本入口文件,导出Store类和install函数等内容
  • index.js:cjs版本入口文件,导出Store类和install函数等内容
  • mixin.js:install函数使用的mixin,执行install函数时,会给Vue实例混入mixin,其内容就定义在该文件
  • module:模块相关的内容,Vuex模块的安装、定义、操作等等都定义在该目录下
    • module-collection.js:模块集合,管理模块,管理模块的嵌套关系,管理模块的初始化,管理模块的注册,管理模块的更新等等
    • module.js:模块,管理单个模块的属性、方法、状态等等
  • plugins:插件相关的内容,Vuex内置提供了devtool插件和logger插件
  • store.js:Store类,Vuex的核心类,用于store的实例化、操作、访问等
  • util.js:工具函数,如参数规范化、断言等等

# 二、install函数

在使用Vuex时,我们通常会在项目的入口文件里写如下的代码:

import Vue from 'vue'
import Vuex from 'vuex'
import store from './store' // 一个使用Vuex.Store实例化的store实例

Vue.use(Vuex) // 安装Vuex插件

// 使用store
new Vue({
  el: '#app',
  store
})

1
2
3
4
5
6
7
8
9
10
11
12

由使用代码可知,Vuex本身是一个Vue插件。而我们都知道,Vue插件要么是一个具有install方法的对象,要么是一个函数,Vue.use方法会调用该函数或者对象的install方法进行安装。我们打开index.js文件`,内容如下:

// index.js
import { Store, install } from './store'
import { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from './helpers'
export default {
  Store,
  install,
  version: '__VERSION__',
  mapState,
  mapMutations,
  mapGetters,
  mapActions,
  createNamespacedHelpers
}
1
2
3
4
5
6
7
8
9
10
11
12
13

入口文件非常简单,主要做了以下几件事:

  1. 导入Store类和install函数
  2. 导入辅助函数,如mapState、mapGetters等辅助函数
  3. 将Store类和install函数以及各种辅助函数导出,供外部使用

执行Vue.use(Vuex)时会执行导出的install方法,其定义在./store.js文件内,接下来,我们来看看install函数的实现。

// store.js
import applyMixin from './mixin'
// 此处省略了其它import导入

// 存放用户安装插件时的Vue构造函数,与Vuex关联
let Vue

export function install(_Vue) {
  if (Vue && _Vue === Vue) { // 已安装
    // 如果不在生产环境中,输出错误信息到控制台
    if (process.env.NODE_ENV !== 'production') {
      console.error(
        '[vuex] already installed. Vue.use(Vuex) should be called only once.'
      )
    }
    // 如果已经安装,则直接返回,避免重复安装
    return
  }
  // 将传入的_Vue实例赋值给全局的Vue变量,完成Vuex和Vue的关联
  Vue = _Vue
  // 应用混合(mixin)到Vue实例中,以集成Vuex的功能
  applyMixin(Vue)
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

代码解析:

  • 首先全局定义了一个Vue变量,该变量由三个作用:
    1. 作为Vuex和Vue的关联,在install函数中,将传入的_Vue构造函数赋值给全局的Vue变量,完成Vuex和Vue的关联;
    2. 作为Vuex的安装状态,在install函数中,判断是否已经安装,如果已经安装,则直接返回,避免重复安装。
  • 接着定义install函数并导出,在install函数中
    • 如果Vue变量已经存在,并且传入的_Vue实例与Vue变量相等,则说明已经安装,则直接返回,避免重复安装。此处需要满足两个条件才会认为已经安装,换句话说,Vuex可以在项目中被安装多次,但是同一个Vue构造函数只能安装一次。
    • 将构造函数Vue赋值给全局的Vue变量,完成Vuex和Vue的关联。
    • 执行applyMixin(Vue)函数,将Vuex混入到Vue实例中,以集成Vuex的功能。

applyMixin函数定义在mixin.js文件中,其内容如下:

export default function (Vue) {
  const version = Number(Vue.version.split('.')[0]) // 获取当前Vue的版本号

  // vue2.0以上则混入beforeCreate钩子函数
  if (version >= 2) {
    Vue.mixin({ beforeCreate: vuexInit })
  } else { // vue1.x
    // override init and inject vuex init procedure
    // for 1.x backwards compatibility.
    // 重写Vue.prototype._init方法
    const _init = Vue.prototype._init
    Vue.prototype._init = function (options = {}) {
      options.init = options.init
        ? [vuexInit].concat(options.init)
        : vuexInit
      _init.call(this, options)
    }
  }

  function vuexInit() {
    //...
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

applyMixin会根据Vuex安装时传入的Vue构造函数获取当前使用的Vue的版本,并且根据不同的版本做不同的操作:在Vue2.0以上版本中,通过混入指向vuexInit函数的beforeCreate钩子函数,这样在Vue实例化时,所有的组件都会执行vuexInit函数,从而完成Vuex的安装;而在Vue2.0以下版本中,则是通过重写Vue.prototype._init方法的方式来实现Vuex的安装,其处理的步骤是首先保存原有的_init的方法,重写的方法中将vuexInit函数添加到options.init数组中,然后调用原有的_init方法,完成Vuex的安装。

接下来看看vuexInit函数的实现:

function vuexInit() {
  // 获取当前实例的$options
  const options = this.$options
  // store injection
  if (options.store) { // 如果有store属性,则注入
    this.$store = typeof options.store === 'function'
      ? options.store()
      : options.store
  } else if (options.parent && options.parent.$store) {
    this.$store = options.parent.$store
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

vuexInit函数做的事情就是将store实例注入到每一个vue实例中,其基本的思路是当前vue实例如果有store则直接注入,否则则从父级实例中获取store实例:

  1. 首先获取当前实例的store属性配置, store = this.$options.store
  2. 如果store存在:
  • 2.1 如果store是一个函数,则将store()的执行返回值赋值给this.$store
  • 2.2 否则,将this.$store = store 如果不存在,则从父级实例中获取$store属性,如果存在,则赋值给this.$store,这确保所有的子组件都能够读取到store实例

之所以子组件可以通过父组件来获取$store是因为在Vue中,创建阶段是先父后子,而挂在阶段是先子后父,所以子组件在beforeCreate钩子中能够读取到父组件的$store属性。如下代码,如果打印出各个组件的生命周期,其结果为:App beforeCreated->App created->App beforeMount->Outer beforeCreated->Outer created->Outer beforeMount->Inner beforeCreated->Inner created->Inner beforeMount->Inner mounted->Outer mounted->App mounted

<APP>
 <Outer>
   <Inner />
 </Outer>
</App>
1
2
3
4
5

# 三、总结

Vuex对外导出了Store类、install函数,以及mapState、mapMutations等辅助辅助函数。以下列代码为例:

import Vue from 'vue'
import Vuex from 'vuex'
import store from './store' // 一个使用Vuex.Store实例化的store实例

Vue.use(Vuex) // 安装Vuex插件

// 使用store
new Vue({
  el: '#app',
  store
})

1
2
3
4
5
6
7
8
9
10
11
12

当执行以上代码时,Vue和Vuex做了以下几件事:

  1. Vue执行Vuex提供的install函数,并将Vue构造函数传入,完成Vuex和Vue的关联。
    1. 已安装,则直接返回,避免重复安装。
    2. 将构造函数Vue赋值给全局的Vue变量,完成Vuex和Vue的关联。
    3. 执行applyMixin(Vue)函数,在Vue2.x版本中混入beforeCreate钩子,在Vue2.0以下使用重写原型的方式修改init钩子,以集成Vuex的功能。
  2. 通过new Store创建了一个store实例,并将store实例注入到Vue实例中。
    1. Vue执行beforeCreate钩子或者_init方法
    2. 当前组件实例有store属性,则注入$store属性,否则从父级实例中获取$store实例属性。

Vuex整体的结构如下图:

vuex结构

接下来,我们逐步解读下new Store(options)时,Vuex是如何处理的。

最近更新: 2024/06/27, 11:47
调式环境准备
ModuleCollection与Module类

← 调式环境准备 ModuleCollection与Module类→

最近更新
01
Axios源码解读
07-29
02
基于React16的Webpack升级与构建速度优化
07-09
03
Vue-Router源码解读
07-09
更多文章>
Theme by Vdoing | Copyright © 2024-2024 深海鱼 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式