Vuex
示意图在说以前先来看一张官方文档提供的一张图html
State
内部预先定义的属性。State
内部属性,则须要调度(Dispatch
)触发 Action
响应状态变化(也可直接提交 mutation
)。mutation
去更改状态(相似发事件)。State
内部属性的更改,触发依赖其属性的组件从新渲染。了解了大体流程,接下来咱们就以基础示例入手,剖析其实现了哪些功能,根据其实现的功能逐步去捋清其代码实现vue
import Vue from 'vue'
import Vuex from 'vuex'
// 注册插件
Vue.use(Vuex)
// 根状态对象。每一个Vuex实例只是一个状态树。
const state = { count: 0 }
// mutations 其实是改变状态的操做。每一个 mutation 处理程序都将整个状态树做为第一个参数,而后是附加的有效负载参数。
// mutations 必须是同步的,而且能够经过插件记录下来,以便调试。
const mutations = {
increment (state) {
state.count++
},
decrement (state) {
state.count--
}
}
// actions 是致使反作用并可能涉及异步操做的函数。
const actions = {
increment: ({ commit }) => commit('increment'),
decrement: ({ commit }) => commit('decrement'),
incrementIfOdd ({ commit, state }) {
if ((state.count + 1) % 2 === 0) {
commit('increment')
}
},
incrementAsync ({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('increment')
resolve()
}, 1000)
})
}
}
// getters are functions
const getters = {
evenOrOdd: state => state.count % 2 === 0 ? 'even' : 'odd'
}
// 模块
const moduleDemo = {
state: { moduleCount: 1 },
mutations: {
// state: 模块的局部状态对象。
moduleIncrement(state) {
state.moduleCount++
},
},
actions: {
moduleIncrement: ({ commit }) => commit('moduleIncrement'),
},
getters: {
moduleCountPlus: ({ moduleCount }) => moduleCount++
}
}
// Vuex实例是经过组合 state、 mutations 、actions 和 getter 建立的。
const store = new Vuex.Store({
state,
getters,
actions,
mutations,
modules: {
moduleDemo
},
})
new Vue({
el: '#app',
store
})
复制代码
在看具体的代码实现以前,咱们大体的先了解一下整个 Vuex
入口文件内的大体内容:git
import devtoolPlugin from './plugins/devtool'
import applyMixin from './mixin'
import { mapState, mapMutations, mapGetters, mapActions } from './helpers'
import { isObject, isPromise, assert } from './util'
let Vue // 绑定安装
// Store 全局单例模式管理
class Store { ... }
// 更新模块
function updateModule(targetModule, newModule) { ... }
// 重置 Store
function resetStore(store) { ... }
// 重置 Store 上 Vue 实例
function resetStoreVM(store, state) { ... }
// 安装模块
function installModule(store, rootState, path, module, hot) { ... }
// 注册 mutations 构造器选项
function registerMutation(store, type, handler, path = []) { ... }
// 注册 action
function registerAction(store, type, handler, path = []) { ... }
// 包装 getters
function wrapGetters(store, moduleGetters, modulePath) { ... }
// 启用严格模式
function enableStrictMode(store) {}
// 获取嵌套的状态
function getNestedState(state, path) {}
// 插件注册方法
function install(_Vue) {}
// 自动注册插件
if (typeof window !== 'undefined' && window.Vue) {
install(window.Vue)
}
export default {
Store,
install,
mapState,
mapMutations,
mapGetters,
mapActions
}
复制代码
在了解了其内部构造,咱们就根据上述梳理,逐点分析其实现。github
咱们知道,Vue
的插件都须要给 Vue
提供一个注册钩子函数 installl
, 执行 Vue.use(Vuex)
实际内部走的是 install
函数的内部调用。vuex
// 插件注册:vue 内部在调用会把 Vue 透传过来
function install(_Vue) {
// 避免重复注册
if (Vue) {
console.error(
'[vuex] already installed. Vue.use(Vuex) should be called only once.'
)
return
}
// 绑定安装
Vue = _Vue
// 应用全局 Vue.mixins
applyMixin(Vue)
}
// 自动注册机制
if (typeof window !== 'undefined' && window.Vue) {
install(window.Vue)
}
复制代码
applyMixin
export default function (Vue) {
// 获取 Vue 版本号
const version = Number(Vue.version.split('.')[0])
// 版本号为 2.x
if (version >= 2) {
// 若存在 init 钩子则把 VuexInit 混入 初始化阶段
// 其它混入 beforeCreate 阶段
const usesInit = Vue.config._lifecycleHooks.indexOf('init') > -1
Vue.mixin(usesInit ? { init: vuexInit } : { beforeCreate: vuexInit })
} else {
// 覆盖 init 并为 1.x 注入 vuex init 过程。 向后兼容性。
const _init = Vue.prototype._init
Vue.prototype._init = function (options = {}) {
options.init = options.init
? [vuexInit].concat(options.init)
: vuexInit
_init.call(this, options)
}
}
/** * Vuex init钩子,注入到每一个实例init钩子列表中。 */
function vuexInit() {
// 获取实例配置参数
const options = this.$options
// 注入 store 实例
if (options.store) {
this.$store = options.store
// 若不存在,则去寻找父级 store 实例
} else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store
}
}
}
复制代码
注:_lifecycleHooks
api
Vue
内部配置项,引用其生命周期相关钩子函数的函数名,因为遗留缘由而暴露的配置项。数组
Vue 2.0.0 ⬆️
其引用为:[
'beforeCreate',
'created',
'beforeMount',
'mounted',
'beforeUpdate',
'updated',
'beforeDestroy',
'destroyed',
'activated', // 激活
'deactivated', // 停用
'errorCaptured' // 捕获错误
]
复制代码
Vue v2.0.0-alpha.6 ⬇️
其引用为:/** * List of lifecycle hooks. */
_lifecycleHooks: [
'init',
'created',
'beforeMount',
'mounted',
'beforeUpdate',
'updated',
'beforeDestroy',
'destroyed',
'activated',
'deactivated'
]
复制代码
若对此有兴趣,能够研究一下 Vue.js
版本的更迭。promise
根据上述分析咱们知道,在注册插件时,根据Vue的不一样版本选择合适的混入时机,使得建立的每一个 Vue
实例在 “初始化阶段” 作些预处理(在实例添加$store
属性,其值为 Store
实例)。那么接下来咱们就具体来看看 Store
内部作了些什么?浏览器
Store
/** * Store * * @class Store 全局单例模式管理 */
class Store {
constructor(options = {}) {
assert(Vue, `在建立商店实例以前必须调用 Vue.use(Vuex)`)
assert(typeof Promise !== 'undefined', `vuex 须要一个 promise polyfill 在这个浏览器。`)
const {
state = {}, // Object | Function Vuex store 实例的根 state 对象
plugins = [], // Array<Function> 一个数组,包含应用在 store 上的插件方法。这些插件直接接收 store 做为惟一参数,能够监听 mutation
strict = false // 严格模式下,任何 mutation 处理函数之外修改 Vuex state 都会抛出错误。
} = options // Vuex.Store 构造器选项
// store 内部状态
this._options = options
// 是否正在提交
this._committing = false
// 存储着 全部 actions
this._actions = Object.create(null)
// 存储着 全部 mutations
this._mutations = Object.create(null)
// 存储着 全部 Getters
this._wrappedGetters = Object.create(null)
this._runtimeModules = Object.create(null)
// 订阅函数池
this._subscribers = []
// 存储着 Vue 实例
this._watcherVM = new Vue()
// bind commit and dispatch to self
const store = this
const { dispatch, commit } = this
// 实例方法 - 分发 action 返回一个解析全部被触发的 action 处理器的 Promise。
this.dispatch = function boundDispatch(type, payload) {
return dispatch.call(store, type, payload)
}
// 实例方法 - 提交 mutation
this.commit = function boundCommit(type, payload, options) {
return commit.call(store, type, payload, options)
}
// 严格模式
this.strict = strict
// init root 模块。这还递归地注册全部子模块,并在 this._wrappedgechers 中收集全部模块 getter
installModule(this, state, [], options)
// 初始化负责反应性的存储vm(也将_wrappedgechers注册为计算属性)
resetStoreVM(this, state)
// 注入应用插件
plugins.concat(devtoolPlugin).forEach(plugin => plugin(this))
}
get state() {
return this._vm.state
}
set state(v) {
assert(false, `使用 store.replacestate() 显式替换存储状态。`)
}
/** * 更改 Vuex 的 store 中的状态的惟一方法是提交 mutation。 * 如:store.commit('increment') * 每一个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。 * 这个回调函数就是咱们实际进行状态更改的地方,而且它会接受 state 做为第一个参数 * * @param {String} type * @param {Object} payload * @param {Object} options * @memberof Store */
commit(type, payload, options) { ... }
// 分发 action
dispatch(type, payload) { ... }
subscribe(fn) { ... }
watch(getter, cb, options) { ... }
// 替换 State
replaceState(state) { ... }
// 注册模块
registerModule(path, module) { ... }
// 注销模块
unregisterModule(path) { ... }
hotUpdate(newOptions) { ... }
// 提交 mutation。
_withCommit(fn) {
// 存储当前提交状态
const committing = this._committing
// 置为提交状态
this._committing = true
// 调用更改状态函数
fn()
// 把提交状态置回原来的状态
this._committing = committing
}
}
复制代码
简单分析梳理:缓存
constructor:
installModule
、 resetStoreVM
等方法,下面将着重看一下其内部实现。为 state
定义了取值函数(getter
)和存值函数(setter
),作一层代理(防止意外的修改)。
定义了一些实例方法 commit
、dispatch
等(以后根据实际调用,具体分析)。
具体实现:
installModule
- 安装模块init root 模块。这还递归地注册全部子模块,并在 this._wrappedgechers 中收集全部模块 getter
/** * 安装模块 * * @param {Object} store Store 实例 * @param {Object | Function} rootState Vuex store 实例的根 state 对象 * @param {Array} path * @param {Object} module Vuex.Store 构造器选项 * @param {*} hot */
function installModule(store, rootState, path, module, hot) {
const isRoot = !path.length // 是不是根仍是模块
// 从 Vuex.Store 构造器选项解构出相关选项
const {
state, // Vuex store 实例的根 state 对象。
actions, // 在 store 上注册 action。处理函数老是接受 context 做为第一个参数,payload 做为第二个参数(可选)。
mutations, // 在 store 上注册 mutation,处理函数老是接受 state 做为第一个参数(若是定义在模块中,则为模块的局部状态),payload 做为第二个参数(可选)。
getters, // 在 store 上注册 getter,getter 方法接受如下参数: state, // 若是在模块中定义则为模块的局部状态. getters, // 等同于 store.getters.
modules // 包含了子模块的对象,会被合并到 store
} = module
// 设置 module 的 state
if (!isRoot && !hot) {
const parentState = getNestedState(rootState, path.slice(0, -1))
const moduleName = path[path.length - 1]
// 为根 State 添加模块的 state
store._withCommit(() => {
Vue.set(parentState, moduleName, state || {})
})
}
// 若存在 mutations 构造器选项 则将其所有选项注册
if (mutations) {
Object.keys(mutations).forEach(key => {
registerMutation(store, key, mutations[key], path)
})
}
// 注册 action
if (actions) {
Object.keys(actions).forEach(key => {
registerAction(store, key, actions[key], path)
})
}
// 包装 getters
if (getters) {
wrapGetters(store, getters, path)
}
// 安装模块
if (modules) {
Object.keys(modules).forEach(key => {
// 递归调用注册每个模块
installModule(store, rootState, path.concat(key), modules[key], hot)
})
}
}
复制代码
由上述初始化调用 installModule(this, state, [], options)
可知其入参,下面就看看各个选项注册的代码实现。
Mutation
、Action
、getter
注册/** * 注册 mutations 构造器选项 * * @param {*} store * @param {*} type * @param {*} handler * @param {*} [path=[]] */
function registerMutation(store, type, handler, path = []) {
const entry = store._mutations[type] || (store._mutations[type] = [])
entry.push(function wrappedMutationHandler(payload) {
handler(getNestedState(store.state, path), payload)
})
}
/** * 注册 action * * @param {Object} store * @param {String} type * @param {Function} handler * @param {Array} [path=[]] */
function registerAction(store, type, handler, path = []) {
const entry = store._actions[type] || (store._actions[type] = [])
const { dispatch, commit } = store
entry.push(function wrappedActionHandler(payload, cb) {
// 注意这里透传的context: 不是 store 实例自己。
dispatch,
commit,
getters: store.getters,
state: getNestedState(store.state, path),
rootState: store.state
}, payload, cb)
// 判断是不是 promise
if (!isPromise(res)) {
res = Promise.resolve(res)
}
// 处理应用的 devtools 插件
if (store._devtoolHook) {
return res.catch(err => {
store._devtoolHook.emit('vuex:error', err)
throw err
})
} else {
return res
}
})
}
/** * 包装 getters * * @param {Object} store * @param {Object} moduleGetters * @param {Array modulePath} */
function wrapGetters(store, moduleGetters, modulePath) {
Object.keys(moduleGetters).forEach(getterKey => {
const rawGetter = moduleGetters[getterKey]
if (store._wrappedGetters[getterKey]) {
console.error(`[vuex] 重复的getter关键: ${getterKey}`)
return
}
store._wrappedGetters[getterKey] = function wrappedGetter(store) {
return rawGetter(
getNestedState(store.state, modulePath), // local state
store.getters, // getters
store.state // root state
)
}
})
}
/** * 获取嵌套的 state * * @param {Object} state * @param {Array} path * @returns */
function getNestedState(state, path) {
return path.length
? path.reduce((state, key) => state[key], state)
: state
}
复制代码
上述代码实现逻辑比较清晰,就是把注册信息添加到 _mutations
、_actions
、 _wrappedGetters
统一管理。
若存在模块则会将其state
添加到 root state
中。
上述基础示例,最终安装结果以下:
_mutations: {
decrement: [
ƒ wrappedMutationHandler(payload)
],
increment: [
ƒ wrappedMutationHandler(payload)
],
moduleIncrement: [
ƒ wrappedMutationHandler(payload)
]
}
_actions: {
decrement: [
ƒ wrappedActionHandler(payload, cb)
],
increment: [
ƒ wrappedActionHandler(payload, cb)
],
incrementAsync: [
ƒ wrappedActionHandler(payload, cb)
],
incrementIfOdd: [
ƒ wrappedActionHandler(payload, cb)
],
moduleIncrement: [
ƒ wrappedActionHandler(payload, cb)
]
}
_wrappedGetters: {
evenOrOdd: ƒ wrappedGetter(store),
moduleCountPlus: ƒ wrappedGetter(store)
}
// root state
state: {
count: 0,
moduleDemo: {
moduleCount: 1
}
}
复制代码
resetStoreVM
- 重置 Store 上 Vue 实例/** * 重置 Store 上 Vue 实例 * * @param {*} store * @param {*} state */
function resetStoreVM(store, state) {
// 取以前的 vue 实例
const oldVm = store._vm
// 绑定存储公共 getter
store.getters = {}
// 获取 Store 中全部 getter
const wrappedGetters = store._wrappedGetters
const computed = {}
// 代理取值函数
Object.keys(wrappedGetters).forEach(key => {
const fn = wrappedGetters[key]
// 利用 computed 的延迟缓存机制
computed[key] = () => fn(store)
// 在公共 getter 上定义以前合并的 getter,并作一层取值代理,实际上取得是计算属性定义的 key 值。
Object.defineProperty(store.getters, key, {
get: () => store._vm[key]
})
})
// 使用 Vue 实例存储状态树抑制警告,以防用户添加了一些 funky global mixins
const silent = Vue.config.silent
// 关闭 Vue 内部的警告
Vue.config.silent = true
// 添加 _vm 属性,值为 Vue 实例
store._vm = new Vue({
data: { state },
computed
})
// 开启 Vue 内部的警告
Vue.config.silent = silent
// 启用严格模式 for new vm
// 严格模式下在非提交的状况下修改 state,抛出错误。
if (store.strict) {
enableStrictMode(store)
}
// 若存在以前的Vue实例
if (oldVm) {
// 在全部订阅的观察者中分派更改,以强制 getter 从新评估。
store._withCommit(() => {
oldVm.state = null
})
// 在下个更新队列以后销毁以前的 Vue 实例
Vue.nextTick(() => oldVm.$destroy())
}
}
/** * 启用严格模式 * * @param {Object} store * @returns {void} * 注:使 Vuex store 进入严格模式,在严格模式下,任何 mutation 处理函数之外修改 Vuex state 都会抛出错误。 */
function enableStrictMode(store) {
store._vm.$watch('state', () => {
assert(store._committing, `不要在 mutation 处理程序以外对 vuex 存储状态进行改变;更改 Vuex 的 store 中的状态的惟一方法是提交 mutation。`)
}, { deep: true, sync: true })
}
复制代码
在讲解具体用例前,先来看看 dispatch
、commit
的代码实现:
dispatch
、commit
- 调度和提交/** * 更改 Vuex 的 store 中的状态的惟一方法是提交 mutation。 * 如:store.commit('increment') * 每一个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。 * 这个回调函数就是咱们实际进行状态更改的地方,而且它会接受 state 做为第一个参数 * * @param {*} type * @param {*} payload * @param {*} options * @memberof Store */
commit(type, payload, options) {
// 检查对象样式提交 如:
// store.commit({ type: 'increment', amount: 10 })
if (isObject(type) && type.type) {
options = payload
payload = type // 使用对象风格的提交方式,整个对象都做为载荷传给 mutation 函数
type = type.type
}
const mutation = { type, payload }
const entry = this._mutations[type] // 查找 mutation
// 若不存在则抛出错误
if (!entry) {
console.error(`[vuex] 未知 mutation 类型: ${type}`)
return
}
// 提交 mutation
this._withCommit(() => {
entry.forEach(function commitIterator(handler) {
handler(payload)
})
})
// 若知足该条件,则:调用订阅池内全部的订阅函数
if (!options || !options.silent) {
this._subscribers.forEach(sub => sub(mutation, this.state))
}
}
/** * 分发 action * * @param {*} type * @param {*} payload * @returns {Promise} 解析全部被触发的 action 处理器的 Promise * @memberof Store */
dispatch(type, payload) {
// check object-style dispatch
// 同上解释
if (isObject(type) && type.type) {
payload = type
type = type.type
}
const entry = this._actions[type]
if (!entry) {
console.error(`[vuex] 未知 action 类型: ${type}`)
return
}
return entry.length > 1
? Promise.all(entry.map(handler => handler(payload)))
: entry[0](payload)
}
复制代码
讲到这里整个流程就已经分析的差很少了。
这里顺便提一下:Mutation 必须是同步函数
devtool
中的 mutation
日志变得不可追踪。【参阅】如下用例演示了从 dispatch
(调度) action
其内部触发 commit
(提交) 进而调用 mutation
状态修改函数, 来达到更新状态。至关清晰👍
<template>
<div id="app">
<div class="root">
Clicked: {{ $store.state.count }} times, count is {{ $store.getters.evenOrOdd }}.
<button @click="$store.dispatch('increment')">+</button>
</div>
<div class="module">
Clicked: {{ $store.state.moduleDemo.moduleCount }} times
<button @click="$store.dispatch('moduleIncrement')">+</button>
</div>
</div>
</template>
复制代码
点击 “+”
调度actions
内部对应的处理函数,其内部去提交状态改变(相似分发事件)在 mutations
内部去执行响应的函数,真正改变状态。状态的改变,致使依赖这些状态的组件更新。“Clicked: 1”
const state = {
count: 0
}
const actions = {
increment: ({ commit }) => commit('increment'),
...
}
const mutations = {
increment (state) {
state.count++
},
...
}
const moduleDemo = {
state: { moduleCount: 1 },
mutations: {
moduleIncrement(state) {
state.moduleCount++
},
},
actions: {
moduleIncrement: ({ commit }) => commit('moduleIncrement'),
},
...
}
复制代码
接下来咱们就来看看咱们在组件中常用的辅助函数实现如:
import {
mapActions,
mapActions,
mapMutations,
mapGetters
} from "vuex";
export default {
computed: {
...mapState([
'count' // 映射 this.count 为 this.$store.state.count
]), // 或 ...mapState({ count: state => state.count })
...mapGetters(["evenOrOdd"]),
},
methods: {
// 必须同步提交
...mapMutations([
'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`
// `mapMutations` 也支持载荷:
'decrement' // 将 `this.decrement(amount)` 映射为 `this.$store.commit('decrement', amount)`
]),
// 处理异步
...mapActions([
"increment",
"decrement",
"incrementIfOdd",
"incrementAsync"
]),
}
};
复制代码
/** * state 映射处理函数 * * @export * @param {Array | Object} states * @returns {Object} */
export function mapState (states) {
const res = {}
normalizeMap(states).forEach(({ key, val }) => {
res[key] = function mappedState () {
return typeof val === 'function'
? val.call(this, this.$store.state, this.$store.getters)
: this.$store.state[val]
}
})
return res
}
/** * 规范参数类型 * * @param {*} map * @returns {Array} */
function normalizeMap(map) {
return Array.isArray(map)
? map.map(key => ({ key, val: key }))
: Object.keys(map).map(key => ({ key, val: map[key] }))
}
复制代码