vuex的原理及实现

基本原理

思考

  1. store是如何被使用到各个组件上的?在每个组件都能使用this.$store进行访问
  2. 为什么state的数据是响应式的?修改state会自动更新视图
  3. 在组件中为什么用this.$store.dispch可以触发store的actions?
  4. 在组件中为什么用this.$store.commit可以触发store的mutations?
  5. 怎么实现state值只允许mutation修改,不允许直接修改?

解答

  • 通过vue的mixin对每个组件挂载this.$store
  • state响应式依靠的vue实例

vuex使用场景

  1. 组件之间需要共享状态
  2. 在实际项目中,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)