前言
使用Vue插件Vuex也有一端时间了.手里的项目我也使用Vuex抽离了一部分Common数据 可能这就是那句 Flux 架构就像眼镜:您自会知道什么时候需要它. 之前在面试的过程中.也被问到过一些感觉挺奇怪的问题.抱着学习的态度.花了几天时间看了下源码了解总结了一下.最近还看到Vuex4也发布了.支持Vue3.具体大家可以看看官方文档.
Vuex是什么?
官方文档上写出:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。 它是"单向数据流"的模式.
数据源状态
State的变动去驱动视图View,我们在视图View上的操作又出发了动作Actions导致了数据源状态State的变化.
- 解决了什么?
当我们多组件状态共享时.也就是说:多个视图依赖于同一状态,来自不同视图的行为需要变更同一状态. 的时候.我们可以使用
Vuex.我们通过维护一个全局的单例模式管理.这样不管组件在哪里,我们都能获取或者触发行为.将开发者的状态集中在数据的更新,而不是数据的传递上去.
面试官的问题
一些当初我被问到我觉得挺奇怪的问题.和我的一些回答.也不知道是否正确.欢迎大家在评论纠正和告诉我更好的回答.
-
Vuex的数据形式是什么样的? 我: 您说的是单向数据流嘛.我理解是一种数据驱动视图.视图变动的动作又改变了视图这一个环形的单向数据流的概念.(就把上面的环形图说了一下子.) -
那
Vue是双向数据绑定的.Vuex是怎么实现的呢?单向数据流和双向绑定又有什么区别呢?
我: Vuex在set state的时候执行了Vue.set()方法实现了响应式定义state.之后我想了下这个问题.Vuex主要是组件间的数据处理.而双向绑定是Vue这种MVVM类型框架的特点.感觉两种不是一种东西.我就说Vuex主要是我们组件间处理状态的时候.构建的一个状态树.而Vue的是视图层面的.之后我就把话题引导了双向绑定的实现上去了.我这边的想法就是.概念上两种没有可比性.希望大家能给我个更好的答案.
- 我们如果不用
Vuex.在window下去维护个状态管理对象不可以么?
我: ... 我当时有点懵.最后这样说的.如果状态树很小的情况下我们当然可以去用事件总线(EventBus)去解决.但是如果是大型的单页面应用的话.我们要维护的就非常大了.不易管理.而且我们也不方便去跟踪数据的动向.Vuex在源码上控制了如果不经过mutations去修改state是会报错的.通过mutations在devtools做了记录.所以我的结论是.小型的没必要使用.可以自己维护.大型的不可以.
- 我们的
state数据为什么要在计算属性computed里面?放在data里面不行么?
我: ... 我又懵了.想了一会.放在data里面的数据是我们进入页面在created里面就定义好的.我们如果动态去更新数据.视图也不会再改变了.而computed里面的数据可以实现一个动态更新的效果.
State
Vuex使用单一状态树.每个应用下只有一个Store实例.State是唯一的数据源的存在.在Vuex中存储的状态对象必须是纯粹的(包含零个或多个key/value键值对).我们在组件中展示状态,最简单的是在计算属性computed中去获取.
const Demo = {
template: `<div>{{ qimukakax }}</div>`
computed: {
qimukakax() {
return this.$store.state.qimukakax
}
}
}
每当store中的数据发生变化的时候,会重新求取,达到更新相关联DOM的效果,个人认为这也是在计算属性中声明,而不在data中声明的原因.如果我们想要获取多个状态的时候.我们可以通过mapState辅助函数去帮助我们.这个函数返回一个对象,我们可以使用对象扩展运算符去简化的配合计算属性使用.
import { mapState } from 'vuex'
export default {
computed: mapState({
qimuakakax: state => state.qimukakax
})
// 还可以这样 computed: mapState(['qimukakax'])
}
看起来这个函数既能接受对象也能接受数组,我们看下mapState的源码到底是怎么样子的:
export const mapState = normalizeNamespace((namespace, states) => {
// 新的对象
const res = {}
// 判断states 是不是数组或对象.不是的话抛出Error!
if (__DEV__ && !isValidMap(states)) {
console.error('[vuex] mapState: mapper parameter must be either an Array or an Object')
}
normalizeMap(states).forEach(({ key, val }) => {
res[key] = function mappedState () {
// 获取根模块的 state 和 getters
let state = this.$store.state
let getters = this.$store.getters
// 存在命名空间的话,去对应的module下去拿到 state 和 getters属性
if (namespace) {
const module = getModuleByNamespace(this.$store, 'mapState', namespace)
if (!module) {
return
}
state = module.context.state
getters = module.context.getters
}
// 此步操作区分了val是字符串还是函数的情况
// qimukakax: state => this.store.state.qimukakax
// qimukakax: 'qimukakax'
return typeof val === 'function'
? val.call(this, state, getters)
: state[val]
}
// mark vuex getter for devtools
// 在devtools中做标记
res[key].vuex = true
})
return res
})
函数首先调用了normalizeMap方法.我们再看一下这个方法:
function normalizeMap (map) {
// 判断states 是不是数组或对象.不是的话返回空数组.
if (!isValidMap(map)) {
return []
}
return Array.isArray(map)
? map.map(key => ({ key, val: key }))
: Object.keys(map).map(key => ({ key, val: map[key] }))
}
首先判断是否是数组.如果是数组.调用数组的map方法转化每个元素变成{key,val: key} 的形式.对象的话就调用Object.keys去遍历对象的每个key,之后再去转换.举个例子:
normalizeMap([1, 2, 3]) => [ { key: 1, val: 1 }, { key: 2, val: 2 }, { key: 3, val: 3 } ]
normalizeMap({a: 1, b: 2, c: 3}) => [ { key: 'a', val: 1 }, { key: 'b', val: 2 }, { key: 'c', val: 3 } ]
回到函数体.我们起初定义了一个空对象.在我们调用了normalizeMap之后.为新的空对象的每个元素都返回新的mappedState函数,之后根据是否在命名空间内获取到state和getters,如果val是函数.就直接调用.把当前 store 上的 state 和 getters 作为参数,得到返回值.否则直接取值this.$store.state[val]作为返回值.
我们虽然使用了Vuex但是我们要注意.并不是使用了就需要把所有的状态都放到Vuex中去管理.应该根据我们实际的开发状况去确定.
Getter
有时候我们要对state中的数据进行一些特殊的操作.要是多个组件都要完成此操作的话.Vuex在store中定义了getter属性.它就像计算属性一样.返回值会被缓存起来.而且只有当依赖值发生变化才会重新去计算.我们可以通过暴露的store.getter对象去访问对应的值.Getter接受两个参数:
getter : {
// 两个参数 state 和 其他的getter属性
doSomething: (state, getter) {
return getter.qimukakax.name
}
}
我们也可以通过getter返回一个函数.并给函数传参.便于我们接下来的操作. 我们也可以通过mapGetters辅助函数去把getter映射到局部的计算属性中,同样支持数组和对象形式传递参数.我们看下源码:
export const mapGetters = normalizeNamespace((namespace, getters) => {
const res = {}
if (__DEV__ && !isValidMap(getters)) {
console.error('[vuex] mapGetters: mapper parameter must be either an Array or an Object')
}
normalizeMap(getters).forEach(({ key, val }) => {
// The namespace has been mutated by normalizeNamespace
val = namespace + val
res[key] = function mappedGetter () {
if (namespace && !getModuleByNamespace(this.$store, 'mapGetters', namespace)) {
return
}
// 对val in this.$store.getters的值坐校验
if (__DEV__ && !(val in this.$store.getters)) {
console.error(`[vuex] unknown getter: ${val}`)
return
}
// 不接受函数的形式.只接受字符串
return this.$store.getters[val]
}
// mark vuex getter for devtools
res[key].vuex = true
})
return res
})
我们可以看到函数的实现和mapState的实现类似.但它的val只能是字符串.并会对val in this.$store.getters的值做校验.我这里举两个例子.
import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters(['qimukakax'])
}
// 等价
// qimuakakax() {
// return this.$store.getters['qimuakakax']
// }
// ...mapGetters({
// qimukakax: 'didididi'
//})
}
我们在看一下getter在store实例中的注册过程:
function registerGetter (store, type, rawGetter, local) {
// 已经存在实例中的.不在重复记录
if (store._wrappedGetters[type]) {
if (__DEV__) {
console.error(`[vuex] duplicate getter key: ${type}`)
}
return
}
// 记录getter
store._wrappedGetters[type] = function wrappedGetter (store) {
return rawGetter(
local.state, // local state
local.getters, // local getters
store.state, // root state
store.getters // root getters
)
}
}
和 registerMutation 以及 registerAction 不同,getters是不允许key重复的.从这里我们也可以看到我们的getters是接受4个参数的.接下来我们看一下它是如何存储的:
function makeLocalGetters (store, namespace) {
// 判断如果没有getters.则创建一个新的
if (!store._makeLocalGettersCache[namespace]) {
const gettersProxy = {}
const splitPos = namespace.length
Object.keys(store.getters).forEach(type => {
// 如果命名空间不匹配.则不操作.
if (type.slice(0, splitPos) !== namespace) return
// 获取本地getter名称
const localType = type.slice(splitPos)
// 为getters添加代理并存储下来.
Object.defineProperty(gettersProxy, localType, {
get: () => store.getters[type],
enumerable: true
})
})
store._makeLocalGettersCache[namespace] = gettersProxy
}
return store._makeLocalGettersCache[namespace]
}
我们在组件中可以通过this.$store.getters.qimukakax访问到对应的回调函数.其实绑定这部分的逻辑在resetStoreVM函数中.在Store的构造函数中.执行了installModule方法后,就会执行resetStoreVM方法:
// 生成一个Vue实例去管理state状态,同时将getters交给computed处理
function resetStoreVM (store, state, hot) {
// 保留现有的store._vm
const oldVm = store._vm
// 在store中声明getters对象
store.getters = {}
// 重置本地缓存
store._makeLocalGettersCache = Object.create(null)
const wrappedGetters = store._wrappedGetters
const computed = {}
// 遍历wrappedGetters.拿到每个getter的包装函数.partial是./until.js中的闭包执行函数.
// 把执行结果用computed临时保存起来.接着用Object.defineProperty()为store.getters定义get方法.
// 当我们在组件中调用this.$store
forEachValue(wrappedGetters, (fn, key) => {
computed[key] = partial(fn, store)
Object.defineProperty(store.getters, key, {
get: () => store._vm[key],
enumerable: true // for local getters
})
})
// 拿到全局的Vue.config.silent的配置.之后临时设置为true.
// 目的是为了取消这个_vm的所有日志和警告.
const silent = Vue.config.silent
Vue.config.silent = true
// 使用Vue实例来存储Vuex的state状态树
// 用computed去缓存getters返回的值
store._vm = new Vue({
data: {
$$state: state
},
computed
})
Vue.config.silent = silent
// 启用警告
if (store.strict) {
enableStrictMode(store)
}
// 如果旧的存在则销毁
if (oldVm) {
if (hot) {
// 解除旧的_vm的引用.
store._withCommit(() => {
oldVm._data.$$state = null
})
}
Vue.nextTick(() => oldVm.$destroy())
}
}
这个方法主要是重置一个私有的_vm对象,它是一个Vue的实例.这个对象会保留我们state的状态树,并利用了计算属性的方式存储了store的getters.具体的部分可以看我代码上面的注释.警告的严格模式主要是:
function enableStrictMode (store) {
store._vm.$watch(function () { return this._data.$$state }, () => {
if (__DEV__) {
assert(store._committing, `do not mutate vuex store state outside mutation handlers.`)
}
}, { deep: true, sync: true })
}
做到了监听store._vm.state的变化,通过store._committing判断state的变化是否是通过mutation.如果是外部直接修改 state,那么 store._committing 的值为 false,这样就抛出一条错误。强调一下,Vuex 中对 state 的修改只能在 mutation 的回调函数里.
Mutations
如同上面说的.更改Vuex的store中状态的唯一方法就是提交mutation.它类似于事件.每个里面都包含一个字符串的事件类型(type) 和一个回调函数(handler) . 并且我们不能直接调用handler.我们要通过调用 store.commit方法:
store.commit('qimukakax')
store.commit接收额外的参数.文档中叫做 载荷(payload) . 大多数情况下.载荷是一个对象.
store.commit('qimukakax', {name: 'qimukakax'})
我们从源码上看一下mutation的注册:
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)
})
}
registerMutation是对store的mutation的初始化,它接受4个参数.store为当前的Store实例.type则为mutation的key.handler为mutation的回调函数.local为当前模块的上下文.我们知道mutation的作用是同步的修改当前的state,函数首先根据type去拿到对应的mutation对象数组.之后把一个mutation的包装函数push到数组中.从这里我们也可以看到.我们的mutations是接受3个参数的.接下来就等待调用的时机.在Vuex中我们是通过commit去调用的.我们看一下commit函数的定义:
commit (_type, _payload, _options) {
// 处理了type为object的情况
const {
type,
payload,
options
} = unifyObjectStyle(_type, _payload, _options)
const mutation = { type, payload }
// 查找是否有对应的mutations不存在就输出错误信息.
const entry = this._mutations[type]
if (!entry) {
if (__DEV__) {
console.error(`[vuex] unknown mutation type: ${type}`)
}
return
}
// 提交mutation
this._withCommit(() => {
entry.forEach(function commitIterator (handler) {
handler(payload)
})
})
// 注册监听store的mutation,和插件相关.
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'
)
}
}
我们看到函数一共接受3个参数.type表示 mutation 的类型,payload 表示额外的参数,options 表示一些配置,比如 silent 等.首先利用工具函数处理了type为object的情况.之后判断是否存在.通过this._withCommit遍历提交commit.这里面的handler(payload)就是我们之前定义的wrappedMutationHandler(payload).相当于执行了回调函数.之后遍历this._subscribers,我们来看一下这是什么:
// 增加mutaitons的监听函数
subscribe (fn, options) {
return genericSubscribe(fn, this._subscribers, options)
}
// 统一封装mutations、actions的监听观察者函数
function genericSubscribe (fn, subs, options) {
if (subs.indexOf(fn) < 0) {
options && options.prepend
? subs.unshift(fn)
: subs.push(fn)
}
return () => {
const i = subs.indexOf(fn)
if (i > -1) {
subs.splice(i, 1)
}
}
}
该方法相当于是接受一个回调函数.以及this._subscribers上.并返回一个函数.个人理解是为了配合插件记录使用的.在使用中我们要注意Mutation必须是同步函数.这里我附上一下文档中的解释:
在组件中我们可以通过commit去提交mutation.或者我们也可以使用mapMutations辅助函数将组件中的methods映射为store.commit调用.举个例子:
import { mapMutations } from 'vuex'
export default {
methods: {
...mapMutations(['qimukakax'])
// this.qimukakax() 映射为 this.$store.commit('qimuakkax')
}
}
我们来看一下mapMutations部分的源码:
export const mapMutations = normalizeNamespace((namespace, mutations) => {
// 新的对象
const res = {}
// 判断states 是不是数组或对象.不是的话抛出Error!
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
})
我们可以发现实现的套路和前面两个辅助函数差不多.区别在于val,那边是commit方法.上边说到Mutation必须是同步的.当然Vuex也为我们提供了异步的解决办法.
Actions
action类似于mutation.但不同的是:
action提交的是mutation,而不是直接改变状态action可以包含任意异步操作
action函数中接受一个和store实例有相同属性方法的context,如果我们想要调用commit可以直接解构出来使用:
actions: {
changeData({commit}) {
commit('qimukakax')
}
}
我们看一下action的注册过程都发生了什么:
function registerAction (store, type, handler, local) {
// 根据type获取action对象数组
const entry = store._actions[type] || (store._actions[type] = [])
// 传入包装函数.
entry.push(function wrappedActionHandler (payload) {
let res = handler.call(store, {
dispatch: local.dispatch,
commit: local.commit,
getters: local.getters,
state: local.state,
rootGetters: store.getters,
rootState: store.state
}, payload)
// 判断是否为Promise 方法在src/until.js
if (!isPromise(res)) {
res = Promise.resolve(res)
}
// 如果开启Vuex devtools 则捕获promise的过程.
// 最终都返回的是res.肯定是一个Promise对象.
if (store._devtoolHook) {
return res.catch(err => {
store._devtoolHook.emit('vuex:error', err)
throw err
})
} else {
return res
}
})
}
registerAction是对store中的action的初始化.它和 registerMutation 的参数一致.不同点在于action是异步的修改state.但这里指的是通过提交mutation去修改.(在Vuex中,mutation是修改state的唯一途径).首先拿到action的对象数组.之后在将一个包装函数传入数组.在handler中我们传入了上文说到的context对象.包括了各种的方法和状态.最后对函数进行Promise判断.确保最后返回的是一个Promise函数.在Vuex中我们通过dispatch这个API去调用下面我们看一下定义:
dispatch (_type, _payload) {
// check object-style dispatch
const {
type,
payload
} = unifyObjectStyle(_type, _payload)
const action = { type, payload }
const entry = this._actions[type]
if (!entry) {
if (__DEV__) {
console.error(`[vuex] unknown action type: ${type}`)
}
return
}
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)
}
}
const result = entry.length > 1
? Promise.all(entry.map(handler => handler(payload)))
: entry[0](payload)
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)
})
})
}
函数的前面部分和commit十分类似.不同点在于后边的如果entry长度为一则直接调用entry[0](payload).这就是上边定义的wrappedActionHandler(payload).以及异步的处理过程.最后都传入this._actionSubscribers监听并做了异常捕获处理.在组件中我们通过mapActions去分发.类似于mapMutations.我们这边直接看下源码:
export const mapActions = normalizeNamespace((namespace, actions) => {
const res = {}
// 判空
if (__DEV__ && !isValidMap(actions)) {
console.error('[vuex] mapActions: mapper parameter must be either an Array or an Object')
}
// 处理后遍历触发dispatch
normalizeMap(actions).forEach(({ key, val }) => {
res[key] = function mappedAction (...args) {
// get dispatch function from store
let dispatch = this.$store.dispatch
if (namespace) {
const module = getModuleByNamespace(this.$store, 'mapActions', namespace)
if (!module) {
return
}
dispatch = module.context.dispatch
}
return typeof val === 'function'
? val.apply(this, [dispatch].concat(args))
: dispatch.apply(this.$store, [val].concat(args))
}
})
return res
})
函数也类似于mapMutations.不同点在于是通过dispatch去映射到methods的.在实际开发中.我建议把一些接口的处理异步操作都放在actions去执行.
Modules
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿,为了解决这个问题.Vuex把store分割成模块.让每个模块有自己的state、mutation、action、getter.并支持了嵌套.
通常情况下局部状态通过context.state暴露,根节点状态通过context.rootState去暴露.在上面的描述中.我们应该都有看到namespaced: true,这是Vuex提供的为了使组件有更高的封装度和复用性.增加的命名空间.当模块被注册后.我们的所有属性都会根据命名空间自动调整命名.如果我们想要在全局命名空间内分发action和提交mutation,我们只需要将{root: true}作为第三个参数给dispatch或commit即可.在组件中使用时.我们也要加上对应的路径信息.
我们看下源码的installModule部分的实现:
// 注册各个模块的信息
function installModule (store, rootState, path, module, hot) {
// 判断是否是根模块
const isRoot = !path.length
// 根据path获取命名空间
const namespace = store._modules.getNamespace(path)
// 如果当前模块设置了namespaced 或 继承了父模块的namespaced,则在modulesNamespaceMap中存储当前模块
if (module.namespaced) {
if (store._modulesNamespaceMap[namespace] && __DEV__) {
console.error(`[vuex] duplicate namespace ${namespace} for the namespaced module ${path.join('/')}`)
}
store._modulesNamespaceMap[namespace] = module
}
// 如果不是根模块的注册
if (!isRoot && !hot) {
const parentState = getNestedState(rootState, path.slice(0, -1))
const moduleName = path[path.length - 1]
store._withCommit(() => {
if (__DEV__) {
if (moduleName in parentState) {
console.warn(
`[vuex] state field "${moduleName}" was overridden by a module with the same name at "${path.join('.')}"`
)
}
}
// 将当前模块的state注册到父模块的state上去并且是响应式的
Vue.set(parentState, moduleName, module.state)
})
}
// 设置当前的上下文
const local = module.context = makeLocalContext(store, namespace, path)
// 分别注册 mutation action getter 并递归注册子模块
module.forEachMutation((mutation, key) => {
const namespacedType = namespace + key
registerMutation(store, namespacedType, mutation, local)
})
module.forEachAction((action, key) => {
// 区分两种写法
// asyncFn(context, payload) {}
// asyncFn:{
// root: true,
// handler(context,payload) {}
// }
const type = action.root ? key : namespace + key
const handler = action.handler || action
registerAction(store, type, handler, local)
})
module.forEachGetter((getter, key) => {
const namespacedType = namespace + key
registerGetter(store, namespacedType, getter, local)
})
// 递归安装子模块.
module.forEachChild((child, key) => {
installModule(store, rootState, path.concat(key), child, hot)
})
}
函数接受5个参数根据语义我们大概知道前面4个的意思.第5个参数表示当动态改变modules或者热更新的时候为true.当我们递归初始化子模块的时候isRoot为false. 进入判断内.这里有getNestedState方法:
// 获取到嵌套的模块中的state
function getNestedState (state, path) {
return path.reduce((state, key) => state[key], state)
}
根据path去寻找state上嵌套的state.计算出当前父模块的state,由于模块的 path 是根据模块的名称 concat 连接的,所以 path 的最后一个元素就是当前模块的模块名.接下来调用 this._withCommit.我们看一下这个方法.
_withCommit (fn) {
const committing = this._committing
this._committing = true
fn()
this._committing = committing
}
由于我们是在修改state,Vuex中所有修改state的地方都会被_withCommit函数包装,保证在同步修改 state 的过程中this._committing的值始终为true。这样当我们观测 state 的变化时,如果 this._committing 的值不为 true,则能检查到这个状态修改是有问题的.接下来我们来看一下local是怎么定义的:
function makeLocalContext (store, namespace, path) {
const noNamespace = namespace === ''
const local = {
dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
const args = unifyObjectStyle(_type, _payload, _options)
const { payload, options } = args
let { type } = args
if (!options || !options.root) {
type = namespace + type
if (__DEV__ && !store._actions[type]) {
console.error(`[vuex] unknown local action type: ${args.type}, global type: ${type}`)
return
}
}
return store.dispatch(type, payload)
},
commit: noNamespace ? store.commit : (_type, _payload, _options) => {
const args = unifyObjectStyle(_type, _payload, _options)
const { payload, options } = args
let { type } = args
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)
}
}
// getters and state object must be gotten lazily
// because they will be changed by vm update
Object.defineProperties(local, {
getters: {
get: noNamespace
? () => store.getters
: () => makeLocalGetters(store, namespace)
},
state: {
get: () => getNestedState(store.state, path)
}
})
return local
}
此函数表示若设置了命名空间则创建一个本地的commit、dispatch方法,否则将使用全局的store.
推荐大家去阅读下官方文档的Modules部分.会加深我们的理解.
Vuex4
- 安装
和Vue3一样它的安装过程有了变动.我们用createStore函数去建立一个store实例.
import { createApp } from 'vue'
import { createStore } from 'vuex'
// Create a new store instance.
const store = createStore({
state () {
return {
count: 0
}
},
mutations: {
increment (state) {
state.count++
}
}
})
const app = createApp({ /* your root component */ })
// Install the store instance as a plugin
app.use(store)
- 使用
在setup中使用.我们可以调用useStore方法.等同于Option API中的this.$store
import { useStore } from 'vuex'
export default {
setup () {
const store = useStore()
}
}
- State和Getters
我们依然利用计算属性去声明:
import { computed } from 'vue'
import { useStore } from 'vuex'
export default {
setup () {
const store = useStore()
return {
qimuakakax: computed(() => store.state.qimukakax),
qimukakax1: computed(() => store.getters.qimukakax1)
}
}
}
- Mutations和Actions
我们直接在setup中使用钩子内部提供的commit和dispatch即可
import { useStore } from 'vuex'
export default {
setup () {
const store = useStore()
return {
qimukakax: () => store.commit('qimukakax'),
qimukakax1: () => store.dispatch('qimukakax1')
}
}
}
- Ts
Vuex4移除了this.$store的全局类型.解决了issue#994.所以我们要自己加一下.
// vuex-shim.d.ts
import { ComponentCustomProperties } from 'vue'
import { Store } from 'vuex'
declare module '@vue/runtime-core' {
// Declare your own store states.
interface State {
count: number
}
interface ComponentCustomProperties {
$store: Store<State>
}
}
写在后面
个人学习笔记.感觉写的挺乱的😂.这也是个人第一次去尝试看下源码.并记下的一点点东西.我觉得在爬坑的路上.去看下我们经常使用的库或者框架的源码还是比较重要的.个人建议先文档了解熟悉,再慢慢啃源码. 我认为我们可以从上面学到很多的东西.编程方法,思想等等.当然不推荐我上边的这个顺序去阅读.按照入口文件部分一个个去看才是正解.