Vuex是vue官方提供的一个状态管理库,采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。自Vue3发布之初,官方也推出了匹配Vue3版本的Vuex4。本文就和大家一起看下Vuex4的设计思想和主要核心API的实现。
Vuex在面试中也是被经常问到的话题,如:
- 组件内部为什么可以访问到Store实例?
- vuex数据响应式原理?
- 了解过vuex的严格模式吗?
- mutations和actions使用的区别?
通过本篇源码的学习,上面的问题就自然清楚了。
在我们的项目应用中,我们大概以下面代码示例方式使用Vuex
// store.js
import { createStore } from 'vuex';
export default createStore({
state: {
num: 1
},
mutations: {},
actions: {},
modules: {}
});
// App.vue
<template>
<div></div>
</template>
<script>
import { defineComponent, computed } from 'vue'
import { useStore } from 'vuex';
export default defineComponent({
setup() {
const store = useStore()
const stateNum = computed(() => store.state.num)
return {stateNum}
}
})
</script>
// main.js
import { createApp } from 'vue';
import App from './App.vue';
import store from './store';
createApp(App).use(store).mount('#app');
1. createStore
createStore方法用来创建Store实例,之后通过调用app.use()注册全局。关于use的源码解读部分请参考之前的一篇文章# Vue3源码之初始化渲染流程解读一。
// src\store.js
export function createStore (options) {
return new Store(options)
}
createStore作为一个方法被导出,接受optios状态配置参数,内部实例一个Store类并返回。
// 只关心核心代码
export class Store {
constructor (options = {}) {
const {
// 用户自定义插件
plugins = [],
// 严格模式
// 在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。
strict = false,
// 打开-关闭devtools
devtools
} = options
// store internal state
this._committing = false
// 保存actions
this._actions = Object.create(null)
// actions 订阅
this._actionSubscribers = []
// 保存mutations
this._mutations = Object.create(null)
// 保存Getter
this._wrappedGetters = Object.create(null)
// 模块化
this._modules = new ModuleCollection(options)
this._modulesNamespaceMap = Object.create(null)
// 保存Store实例订阅
this._subscribers = []
this._makeLocalGettersCache = Object.create(null)
// 是否开启开发调试工具
this._devtools = devtools
// this指向Store实例
// Store实例绑定commit和dispatch方法,在options api const store = useStore()使用commit和dispatch方法
const store = this
const { dispatch, commit } = this
this.dispatch = function boundDispatch (type, payload) {
return dispatch.call(store, type, payload)
}
this.commit = function boundCommit (type, payload, options) {
return commit.call(store, type, payload, options)
}
this.strict = strict
// 获取用户声明state
const state = this._modules.root.state
// 注册配置module
installModule(this, state, [], this._modules.root)
// 初始化state
resetStoreState(this, state)
// 注册组件
plugins.forEach(plugin => plugin(this))
}
install (app, injectKey) {
// provide 为 composition API 中使用
// 可以传入 injectKey 如果没传取默认的 storeKey 也就是 store
app.provide(injectKey || storeKey, this)
// 为 option API 中使用
app.config.globalProperties.$store = this
}
// 类属性getter
get state () {
return this._state.data
}
// 修改拦截
set state (v) {
if (__DEV__) {
assert(false, `use store.replaceState() to explicit replace store state.`)
}
}
commit (_type, _payload, _options) {
// 检查commit调用参数方式,统一调用参数
const {
type,
payload,
options
} = unifyObjectStyle(_type, _payload, _options)
const mutation = { type, payload }
// 依据commit提交参数type查找mutations配置对应处理方法
const entry = this._mutations[type]
if (!entry) {
// 开发环境,为找到对应处理函数--提示
if (__DEV__) {
console.error(`[vuex] unknown mutation type: ${type}`)
}
return
}
// 循环处理mutations绑定函数
this._withCommit(() => {
entry.forEach(function commitIterator (handler) {
handler(payload)
})
})
// 通知订阅
this._subscribers
.slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
.forEach(sub => sub(mutation, this.state))
if (
__DEV__ &&
options && options.silent
) {
console.warn(
`[vuex] mutation type: ${type}. Silent option has been removed. ` +
'Use the filter functionality in the vue-devtools'
)
}
}
dispatch (_type, _payload) {
// 检查dispatch调用参数方式并统一化
const {
type,
payload
} = unifyObjectStyle(_type, _payload)
const action = { type, payload }
// 依据action调用参数type获取actions声明处理函数
const entry = this._actions[type]
if (!entry) {
// 开发环境-未找到对应处理函数-提示
if (__DEV__) {
console.error(`[vuex] unknown action type: ${type}`)
}
return
}
// 处理订阅action
try {
this._actionSubscribers
.slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
.filter(sub => sub.before)
.forEach(sub => sub.before(action, this.state))
} catch (e) {
if (__DEV__) {
console.warn(`[vuex] error in before action subscribers: `)
console.error(e)
}
}
// 执行actions处理函数
const result = entry.length > 1
? Promise.all(entry.map(handler => handler(payload)))
: entry[0](payload)
// 返回所有被触发action promise
return new Promise((resolve, reject) => {
result.then(res => {
try {
this._actionSubscribers
.filter(sub => sub.after)
.forEach(sub => sub.after(action, this.state))
} catch (e) {
if (__DEV__) {
console.warn(`[vuex] error in after action subscribers: `)
console.error(e)
}
}
resolve(res)
}, error => {
try {
this._actionSubscribers
.filter(sub => sub.error)
.forEach(sub => sub.error(action, this.state, error))
} catch (e) {
if (__DEV__) {
console.warn(`[vuex] error in error action subscribers: `)
console.error(e)
}
}
reject(error)
})
})
}
}
源码中我们看到,向Vue中注册Vuex插件主要提供两种访问store方法:
- 基于provide/inject,供我们在setup中使用
- 添加全局实例属性$store,用以在组件内部通过this访问
2. 响应式数据处理
在上面的createStore源码中,有其中一步操作用来初始化state数据,看下resetStoreState方法
import { reactive} from 'vue'
export function resetStoreState (store, state, hot) {
// 获取就state
const oldState = store._state
store.getters = {}
// 数据响应式
store._state = reactive({
data: state
})
// 开发环境下,热更新
if (oldState) {
if (hot) {
store._withCommit(() => {
oldState.data = null
})
}
}
}
在这里我们看到,Vuex中的state数据响应式主要依赖vue3响应式API reactive来创建
3. useStore
useStore是针对在vue3中采用组合式api的使用方式提供的hook api
// src\injectKey.js
import { inject } from 'vue'
// 默认store key
export const storeKey = 'store'
export function useStore (key = null) {
return inject(key !== null ? key : storeKey)
}
useStore的实现非常简单,在createStore源码中通过provide方式暴露Store实例,在组件组合式api中,通过inject获取到对应Store实例。
说明:provide/inject是vue中用以向深层组件传递数据的一种方法。provide/inject绑定的数据本身并不具有响应式,但是我们通过provide传递的数据如果为一个响应式数据,那么inject获取到的数据就是响应式数据。