Vuex 是专为 Vue.js 应用程序开发的状态管理模式,它可以实现我们 Vue 中跨级组件的通信,也可以存储全局共享的状态;它主要通过 state
来定义状态,通过 commit
和 dispatch
来同步或异步修改状态;
一、基础用法
- 安装依赖:
npm install vuex
; - 编写
store
实例并导出;
// src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
// 全局状态
state: {
count: 1
},
// 存储同步修改 state 中变量的方法
mutations: {
},
// 存储异步操作的方法
actions: {
},
// 相当于 computed 的作用
getters: {
},
// 子模块
modules: {
}
})
- 在 main.js 中进行注册;
// main.js
import Vue from 'vue'
import store from './store/index.js'
import App from './App.vue'
new Vue({
store,
render: h => h(App)
}).$mount('#app')
- 在页面中使用:
(1)在 computed
属性中进行定义
<script>
export default {
computed: {
count() {
return this.$store.state.count
}
}
}
</script>
(2)通过 mapState
获取
<script>
import { ...mapState } from 'vuex'
export default {
computed: {
// 方式一
...mapState([
'count'
])
// 方式二
//...mapState({
// count: state => state.count
//})
// 方式三: 命名空间
//...mapState('xxx', [
// 'count'
//])
}
}
</script>
二、基本原理
了解完基础用法之后,接下来通过源码来理解其内部实现;
- 首先是通过
Vuex.use(Vuex)
来进行插件安装,那我们知道Vue.use
是通过调用插件的install
方法来安装,那么 Vuex 的实现需要有一个install
方法; - 实例化 Vuex 是
new Vuex.Store({})
,则需要有一个Store
类或方法; mapState
、mapMutations
、mapActions
和mapGetters
实现;
1、install 方法
// src/store.js
export function install(_Vue) {
if (Vue && _Vue === Vue) {
if (__DEV__) {
console.error(
'[vuex] already installed. Vue.use(Vuex) should be called only once.'
)
}
return
}
Vue = _Vue
// 注入的核心
applyMixin(Vue)
}
// src/mixin.js
export default function (Vue) {
const version = Number(Vue.version.split('.')[0])
// 版本判断,如果是 2.x,则在全局 beforeCreate 生命周期中插入
if (version >= 2) {
Vue.mixin({
beforeCreate: vuexInit })
} else {
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 () {
const options = this.$options
// 根实例注入 $store
if (options.store) {
this.$store = typeof options.store === 'function'
? options.store()
: options.store
} else if (options.parent && options.parent.$store) {
// 从父节点获取 store 实例
this.$store = options.parent.$store
}
}
}
可以看到 install
做的事情比较简单,install
方法主要将 $store
注入到组件或页面中,2.x 版本及以上是通过 beforeCreate
注入,其他的则通过在 init
方法中注入,类似于 $mount
方法;
2、Store 类
Store 类主要是对传入的 options
进行处理,包括模块收集、安装模块、处理父子模块之间的关系等,可以参考简略版vuex 的代码,需要注意的是命名空间的处理,调用孙子模块需要确保父模块的路径名称正确;
3、mapState、mapMutations、mapActions、mapGetters 的实现
mapState、mapMutations、mapActions、mapGetters
工具方法可以帮助我们一次性获取多个 store 中的变量和方法,内部实现也比较简单,主要分几个步骤:
- 对用户传入的参数进行处理
normalizeNamespace
; - 对用户传入的参数进行格式化
normalizeMap
,然后循环遍历取值赋值给返回结果; - 如果有命名空间,则根据命名空间获取相应的模块
getModuleByNamespace
,返回该模块的数据; - 不同类型的实现可以参考本文第二点代码仓库的代码,位置在
helper.js
文件;
// 将数据转化为 map, 格式为 { key, val }
// (1) 参数为数组: mapState(['xxx'])
// (2) 参数为对象: mapState({ xxx: store => store.state.xxx })
function normalizeMap(map) {
return Array.isArray(map)
? map.map(key => ({
key, val: key }))
: Object.keys(map).map(key => ({
key, val: map[key] }))
}
// 对参数和 namespace 进行处理
// (1) 不带命名空间: mapState([])
// (2) 带命名空间: mapState('namespace', []), 对命名空间进行处理,保持 namespace/ 的格式
function normalizeNamespace(fn) {
return (namespace, map) => {
if (typeof namespace !== 'string') {
map = namespace
namespace = ''
} else if (namespace.charAt(namespace.length - 1) !== '/') {
namespace += '/'
}
return fn(namespace, map)
}
}
// 根据命名空间获取相应的模块
function getModuleByNamespace(store, helper, namespace) {
const module = store._modulesNameSpaceMap[namespace]
if (!module) {
console.error(`[vuex] module namespace not found in ${
helper}(): ${
namespace}`)
}
return module
}
三、总结
Vuex 是专门为 Vue 提供状态管理的插件,注册之后可以在全局访问到 $store
实例,同时也提供一些 api 来辅助我们获取实例中的状态和状态变更的方法,其内部实现主要是根据传入的 options
来进行模块间关系的处理,也可以通过命名空间来快速访问状态,方便我们在日常开发使用;