作者:chinamasters
来源:SegmentFault 思否社区
在阅读Vuex源码之前,因为Vuex的api和使用功能略微复杂,默认以为实现起来相当复杂,望而生畏。然而通过深入学习源码,发现核心功能结合vue实现起来非常巧妙,也就核心几行代码,直呼内行。本文也就100左右行代码就能快速手写自己的Vuex代码!
前言
Vuex 是⼀个专为 Vue.js应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以⼀种可预测的方式发生变化。那么Vuex和单纯的全局对象有什么不同呢?
Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变 化,那么相应的组件也会相应地得到高效更新。
不能直接改变 store 中的状态。改变 store 中的状态的唯⼀途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每⼀个状态的变化,从而让我们能够实现⼀些工具帮助我 们更好地了解我们的应用。
通过以上两点认知我们来快速实现自己的Vuex!
Vuex 初始化
为什么在vue实例化的时候要传入store去实例化呢?那是为了让vue所有的组件中可以通过 this.$store来获取该对象,即 this.$store 指向 store 实例。
// Store 待实现
const store = new Store({
state: {
count: 0,
num: 10
})
new Vue({
el: '#app',
store: store // 此处的 store 为 this.$options.store
})
Vuex提供了install属性,通过Vue.use(Vuex)来注册。
const install = function (Vue) {
Vue.mixin({
beforeCreate() {
if (this.$options.store) {
Vue.prototype.$store = this.$options.store
}
}
})
}
Vue全局混⼊了⼀个 beforeCreated 钩⼦函数, options.store 保存在所有组件的 this.$store 中,这个 options.store 就是我们在实例化 Store 对象的实例。Store 对象的构造函数接收⼀个对象参数,它包含 actions 、 getters 、 state 、 mutations 等核⼼概念,接下来我们一一实现。
Vuex state
其实 state 是 vue 实例中的 data ,通过 Store 内部创建Vue实例,将 state 存储到 data 里,然后改变 state 就是触发了 data 数据的改变从而实现了视图的更新。
// 实例化 Store
const store = new Store({
state: {
count: 0,
num: 10
}
})
// Store 实现
class Store {
constructor({state = {}}) {
this.vm = new Vue({
data: {state} // state 添加到 data 中
})
}
get state() {
return this.vm.state // 将 state代理到 vue 实例中的 state
}
set state(v) {
console.warn(`Use store.replaceState() to explicit replace store state.`)
}
}
由上可知,store.state.count 等价于 store.vm.state。不论是获取或者改变state里面的数据都是间接的触发了vue中data数据的变化,从而触发视图更新。
Vuex getters
知道state 是vue实例中的data,那么同理,getters 就是 vue中的计算属性 computed。
// 实例化 Store
const store = new Store({
state: {
count: 0,
num: 10
},
getters: {
total: state => {
return state.num + state.count
}
},
})
// Store 实现
class Store {
constructor({state = {}, getters = {}}) {
this.getters = getters
// 创建模拟 computed 对象
const computed = {}
Object.keys(getters).forEach(key => {
const fn = getters[key]
// 入参 state 和 getters
computed[key] = () => fn(this.state, this.getters)
// 代理 getters 到 vm 实例上
Object.defineProperty(this.getters, key, {
get: () => this.vm[key]
})
})
// 赋值到 vue 中的 computed 计算属性中
this.vm = new Vue({
data: {
state,
},
computed,
})
}
get state() {
return this.vm.state
}
set state(v) {
console.warn(`Use store.replaceState() to explicit replace store state.`)
}
}
使用 Object.defineProperty 将getters上的所有属性都代理到了vm实例上的computed计算属性中,也就是 store.getters.count 等价于 store.vm.count。
Vuex mutations
mutations等同于发布订阅模式,先在mutations中订阅事件,然后再commit发布事件。
// 实例化 Store
const store = new Store({
state: {
count: 0,
num: 10
},
mutations: {
INCREASE: (state, n) =>{
state.count += n
},
DECREASE: (state, n) =>{
state.count -= n
}
}
})
// Store 实现
class Store {
constructor({state = {}, mutations = {}, strict = false}) {
this.mutations = mutations
// 严格摸索只能通过 commit 改变 state
this.strict && this.enableStrictMode()
}
commit(key, payload) {
// 获取事件
const fn = this.mutations[key]
// 开始 commit
this.committing = true
// 执行事件 并传参
fn(this.state, payload)
// 结束 commit 所以说明 commit 只能执行同步事件
this.committing = false
}
enableStrictMode () {
// vm实例观察 state 是否由 commit 触发改变
this.vm.$watch('state', () => {
!this.committing
&&
console.warn(`Do not mutate vuex store state outside mutation handlers.`)
}, { deep: true, sync: true })
}
get state() {
return this.vm.state
}
set state(v) {
console.warn(`Use store.replaceState() to explicit replace store state.`)
}
}
store.commit 执行 mutations中的事件,通过发布订阅实现起来并不难。Vuex中的严格模式,只能在commit的时候改变state数据,不然提示错误。
Vuex actions
mutations 用于同步更新 state,而 actions 则是提交 mutations,并可进行异步操作,从而间接更新 state。
// 实例化 Store
const store = new Store({
actions: {
getToal({dispatch, commit, state, getters}, n){
return new Promise(resolve => {
setTimeout(() => {
commit('DECREASE', n)
resolve(getters.total)
}, 1000)
})
}
}
})
// Store 实现
class Store {
constructor({actions = {}}) {
this.actions = actions
}
dispatch(key, payload) {
const fn = this.actions[key]
const {state, getters, commit, dispatch} = this
// 注意 this 指向
const result = fn({state, getters, commit: commit.bind(this), dispatch: dispatch.bind(this)}, payload)
// 返回 promise
return this.isPromise(result) ? result : Promise.resolve(result)
}
// 判断是否是 promise
isPromise (val) {
return val && typeof val.then === 'function'
}
}
mutations 和 actions 的实现大同小异,actions 核心在于处理异步逻辑,并返回一个 promise。
完整案例代码
这边把以上的代码统一归纳起来,可以根据这份完整代码来分析Vuex逻辑。
html>
"en">
"UTF-8">
"viewport" content="width=device-width, initial-scale=1.0">
Document
"app">-a>-a>
总结
通过上面的完整案例可知,Vuex核心代码也就100行左右,但是他巧妙的结合了vue的data和computeds属性,化繁为简,实现了复杂的功能,所以说vuex是不能脱离vue而独立运行的。
本文是结合官网源码提取核心思想手写自己的Vuex,而官网的Vuex,为了避免store结构臃肿,还实现了modules等功能,具体实现可以查看Vuex官网源码。
- END -