vuex的初始化
- vuex通过export default一个object{},里面包含了许多的对象,其中就包含了
Store
,这是是一个构造函数,用于接收配置的state,mutation之类的参数,使用createStore
都会重新new一个store对象,所以不能重复new。
// 这里必定暴露一个install的方法,因为vuex本身是一个对象,并不是一个function--构造函数内部有一个install方法,并且还在定义了$store为全局用法。
import { createApp } from 'vue'
import { createStore } from 'vuex'
// 创建一个新的 store 实例,
const store = createStore({
state () {
return {
count: 0
}
},
mutations: {
increment (state) {
state.count++
}
}
})
const app = createApp({ /* 根组件 */ })
// 将 store 实例作为插件安装
app.use(store)
devtools,vuex面板
- 在install的同时添加到vue开发面板上面,同步实时观察数据的变化,获得其快照。为某个特定的 Vuex 实例打开或关闭 devtools。对于传入 false 的实例来说 Vuex store 不会订阅到 devtools 插件。对于一个页面中有多个 store 的情况非常有用。
const useDevtools = this._devtools !== undefined
? this._devtools
: __DEV__ || __VUE_PROD_DEVTOOLS__
if (useDevtools) {
addDevtools(app, this)
}
ModuleCollection收集默认模块,进行命名区分
-
通过
ModuleCollection
来注册模块,包括内嵌的模块,进行命名区分
。分辨各个模块的数据。 -
通过
new Module(rawModule, runtime)
来初始化每个模块的内容,通过判断path的length来辨别是否是根级模块,后续通过判断rawModule.modules
来识别当前模块是否包含子模块,如果包含子模块,则循环该子模块进行重新register
.
register (path, rawModule, runtime = true) {
if (__DEV__) {
assertRawModule(path, rawModule)
}
const newModule = new Module(rawModule, runtime) // 初始化每个模块的数据
if (path.length === 0) { // 这个是根目录
this.root = newModule
} else {
const parent = this.get(path.slice(0, -1))
parent.addChild(path[path.length - 1], newModule)
}
// register nested(嵌套) modules
if (rawModule.modules) {
forEachValue(rawModule.modules, (rawChildModule, key) => {
this.register(path.concat(key), rawChildModule, runtime)
})
}
}
installModule安装模块
-
将通过
ModuleCollection
收集到的命名模块
进行初始化,同时也将递归其子模块并将其初始化。 -
每层modul都会有context属性,是将当前module里面的所有数据都集合放在context存储。
const local = module.context = makeLocalContext(store, namespace, path)
-
通过对当前空间的module进行遍历注册,
forEachMutation
,forEachAction
,forEachGetter
分别注册旗下的三大属性,对于还有子module的还需通过forEachChild
重新遍历子module,进行调用installModule
进行初始化。 -
下面以mutation为案例,说明mutation里面的方法的传参是如何使用的。
mutation: {
test (state, payload) {
// state为当前module里面的state数据,可以直接对state进行修改
// payload则是传进的参数
}
}
/*** 源码解析
* @params {Object} store - 当前store的实例
* @params {String} type - 命名空间类型
* @params {Function} handler - 对应的mutation里面的函数
* @params {Object} local - 当前的处理过的context
**/
function registerMutation (store, type, handler, local) {
const entry = store._mutations[type] || (store._mutations[type] = [])
entry.push(function wrappedMutationHandler (payload) {
handler.call(store, local.state, payload) // 这里可以知道mutation里面的方法的入参定义
})
}
commit的提交使用
- commit的提交方式有两种,具体如下
// 第一种,第一个参数是type,第二个参数就是payload
store.commit('increment', {
amount: 10
})
// 第二种,把type和payload都放在了一个object里面
store.commit({
type: 'increment',
amount: 10
})
// 源码里对着两种方式都做了兼容,第二种是为了更加贴切用户的使用习惯
export function unifyObjectStyle (type, payload, options) {
if (isObject(type) && type.type) { // 当type是对象的时候,会进行额外的处理
options = payload
payload = type
type = type.type
}
return { type, payload, options }
}
- 如果想要在带命名空间的模块内访问全局内容,可以在options里面添加
root = true
commit('rootMutation', null, { root: true }) // -> 'rootMutation',直接访问顶级的方法
// 源码
commit: noNamespace ? store.commit : (_type, _payload, _options) => {
const args = unifyObjectStyle(_type, _payload, _options)
const { payload, options } = args
let { type } = args
// 这里判断options里面的root是否为true,如果不为true的话,就使用命名空间的方式获取方法名。
if (!options || !options.root) {
type = namespace + type
if (__DEV__ && !store._mutations[type]) {
console.error(`[vuex] unknown local mutation type: ${args.type}, global type: ${type}`)
return
}
}
store.commit(type, payload, options)
}
通过resetStoreState对数据进行监听
- 通过vue3里面的reactive方法将store里面的state全部变成响应式的数据
store._state = reactive({
data: state
})
- 把所有的getters都收集到
this._wrappedGetters
里面,getters还是使用Object.defineProperty进行数据监听,只有get方法,没有set方法,不允许通过getters修改数据。
scope.run(() => {
forEachValue(wrappedGetters, (fn, key) => {
// use computed to leverage its lazy-caching mechanism
// direct inline function use will lead to closure preserving oldState.
// using partial to return function with only arguments preserved in closure environment.
computedObj[key] = partial(fn, store)
computedCache[key] = computed(() => computedObj[key]())
Object.defineProperty(store.getters, key, {
get: () => computedCache[key].value, // 仅仅支持get
enumerable: true // for local getters
})
})
})
plugins的使用
- vuex里面允许开发者开发自己的插件,插件只需要接收一个参数,那就是store的实例对象。开发者可以通过实例对象,对其进行修改。
plugins.forEach(plugin => plugin(this)) // 插件必须是一个函数,不像vue一样接受对象或者函数
辅助函数的使用
-
这里举例说明一下
mapMutations
,辅助函数可以在vue组件里面快速访问vuex的mutation里面的方法。 -
因为有命名空间的存在,所以先要判断是否有命名空间:
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)
}
}
- 在没有命名空间的时候,有两种方法,一种是
array
类型,一种是object
类型:
import { mapMutations } from 'vuex'
// 没有命名空间
export default {
// ...
methods: {
...mapMutations([
'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`
// `mapMutations` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
]),
...mapMutations({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
})
}
}
// 这是有命名空间的
export default {
methods: {
...mapmutations([
'some/nested/module/foo', // -> this['some/nested/module/foo']()
'some/nested/module/bar' // -> this['some/nested/module/bar']()
])
},
...mapmutations('some/nested/module', [
'foo', // -> this.foo()
'bar' // -> this.bar()
])
}
// 源码
function normalizeMap (map) { // 这个方法判断是数组还是object类型,并且将其拼凑起来
if (!isValidMap(map)) {
return []
}
return Array.isArray(map)
? map.map(key => ({ key, val: key }))
: Object.keys(map).map(key => ({ key, val: map[key] }))
}
- mapMutations的源码,通过获取store里面匹配到的mutation里面方法,然后返回出去。
export const mapMutations = normalizeNamespace((namespace, mutations) => {
const res = {}
if (__DEV__ && !isValidMap(mutations)) {
console.error('[vuex] mapMutations: mapper parameter must be either an Array or an Object')
}
normalizeMap(mutations).forEach(({ key, val }) => {
res[key] = function mappedMutation (...args) { // 这里匹配到了组件定义的方法
// Get the commit method from store
let commit = this.$store.commit
if (namespace) {
const module = getModuleByNamespace(this.$store, 'mapMutations', namespace)
if (!module) {
return
}
commit = module.context.commit
}
return typeof val === 'function'
? val.apply(this, [commit].concat(args))
: commit.apply(this.$store, [val].concat(args))
}
})
return res
})