vuex的原理及实现
基本原理
思考
- store是如何被使用到各个组件上的?在每个组件都能使用this.$store进行访问
- 为什么state的数据是响应式的?修改state会自动更新视图
- 在组件中为什么用this.$store.dispch可以触发store的actions?
- 在组件中为什么用this.$store.commit可以触发store的mutations?
- 怎么实现state值只允许mutation修改,不允许直接修改?
解答
- 通过vue的mixin对每个组件挂载this.$store
- state响应式依靠的vue实例
vuex使用场景
- 组件之间需要共享状态
- 在实际项目中,ajax请求来的数据我也会放到store维护,因为这样数据逻辑和页面可以分离,页面不用关心如何加载和处理数据,只需要简单挂载。
vuex组成部分
state:通过new Vue()实现数据响应式,通过mixin对每一个组件实现this.$store访问
getters:通过Object.defineProperty实现get取值
mutations:实现store.commit(type, payload)即可
actions:实现store.dispatch(type, payload)即可
实现简单版vuex
// simpleVuex.js
import Vue from "vue"
export class Store {
constructor(options) {
let { state = {}, getters = {}, mutations = {}, actions = {} } = options
// 是否正在commit
this._isCommitting = false
// 将state变成响应式数据,使得在改变state的时候可以自动更新视图
this._vm = new Vue({
data() {
return {
state
}
},
watch: {
// 监听state变化,只允许通过mutation修改,不允许直接修改
state: {
deep: true,
sync: true, // 是否同步执行handler,必须为true,否则在commit之后的handler是异步执行的话,isCommitting值不准确
handler: () => {
// 不是通过commit修改的
if (!this._isCommitting) {
console.error('state只能通过提交mutations修改')
}
}
}
}
})
this._mutations = mutations
this._actions = actions
// 用Object.defineProperty对getters进行劫持,使得可以通过store.getters.countText进行访问
this.getters = {}
for (let key in getters) {
let valueFn = getters[key]
Object.defineProperty(this.getters, key, {
configurable: true,
enumerable: true,
get: () => {
return valueFn(this.state)
}
})
}
}
// 通过store.state.count访问
get state() {
return this._vm.state
}
// commit mutation, 方法store.commit(type, payload)
commit(type, payload) {
const committing = this._isCommitting
this._isCommitting = true // 正在提交
this._mutations[type](this.state, payload)
this._isCommitting = committing // 提交完成
}
// dispatch action, 方法store.dispatch(type, payload)
dispatch(type, payload) {
this._actions[type]({
commit: this.commit.bind(this), // 注意绑定this上下文
state: this.state
}, payload)
}
}
const store = new Store({
state: {
count: 0
},
getters: {
countText(state) {
return state.count + '秒'
}
},
mutations: {
setCount(state, payload) {
state.count += payload
}
},
actions: {
setCountAsync({ commit, state }, payload) {
setTimeout(() => {
commit('setCount', payload)
}, 1000)
}
}
})
// 注册vuex插件
export const installVuex = {
install() {
Vue.prototype.$store = store
}
}
// main.js
import {installVuex} from './simpleVuex'
Vue.use(installVuex)