参考文档:

Vuex是什么

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

这个状态自管理应用包含以下几个部分:

  • 状态,驱动应用的数据源;
  • 视图,以声明方式将状态映射到视图;
  • 操作,响应在视图上的用户输入导致的状态变化。

Vuex的工作原理如下图所示:
Vuex

Vuex4.x的简单用法

创建一个 store 实例

import { createApp } from 'vue'
import { createStore } from 'vuex'

// 创建一个新的 store 实例
const store = createStore({
  state () {
    return {
      count: 0
    }
  },
  getters: {
      double(state){
          return state.count * 2
      }
  },
  mutations: {
    increment (state, payload) {
      state.count++
    }
  },
  actions: {
     AsyncIncrement(state, payload){
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                commit('someMutation', payload)
                resolve()
            }, 1000)
        })
     }
  }
})

const app = createApp({ /* 根组件 */ })

// 将 store 实例作为插件安装
app.use(store)

通过组合式API的方法使用

访问 State 和 Getter

import { computed } from 'vue'
import { useStore } from 'vuex'

export default {
  setup () {
    const store = useStore()

    return {
      // 在 computed 函数中访问 state
      count: computed(() => store.state.count),

      // 在 computed 函数中访问 getter
      double: computed(() => store.getters.double)
    }
  }
}

访问 Mutation 和 Action

import { useStore } from 'vuex'

export default {
  setup () {
    const store = useStore()

    return {
      // 使用 mutation
      increment: () => store.commit('increment'),

      // 使用 action
      asyncIncrement: () => store.dispatch('asyncIncrement')
    }
  }
}

从用法上对比Vuex3.x

  • 创建 store 的方法不同,之前是通过new Store() 来创建一个 store实例。现在是通过 createStore 方法来创建。
  • Vuex4.x兼容之前的使用方法。但是在Vue3.x中如果我们想要通过组合式API的方法使用Vuex,我们需要借助与 useStore 这个函数。

实现一个简单的 Vuex4.x;

实现一个 createStore方法

store实际上还是基于一个Class来实现的,只是没有直接将 Store 这个类暴露出来,而是包装成了一个函数,直接返回实例。

import { reactive } from 'vue'

export function createStore(options){
    return new Store(options)
}

class Store{
    constructor(options){
        const store = this;
        // 使用reactive使state是响应式的
        store._state = reactive({data: options.state})
    }
    get state(){ 
        return this._state.data
    }
}

通过 Object.defineProperty 来重新定义getters

将用户传入的getters 全部定义到store实例的getters属性上,在获取值的时候调用

function forEachValue(obj, fn){
    Object.keys(obj).forEach(key => fn(obj[key], key))
}

class Store{
    constructor(options){
        const store = this;

        // 使用reactive使state是响应式的
        store._state = reactive({data: options.state})
        
        // 保存getters
        const _getters = options.getters;

        store.getters = {};

        forEachValue(_getters, function(fn, key){
            Object.defineProperty(store.getters, key, {
                get: ()=> fn(store.state),
                enumerable: true
            })
        })
    }
}

基于发布订阅模式实现 mutationsactions

class Store{
    constructor(options){
        // ...
        const store = this;
        store._mutations = Object.create(null)
        const _mutations = options.mutations;

        forEachValue(_mutations, (mutation, key)=>{
            store._mutations[key] = (payload) => {
                mutation.call(store, store.state, payload)
            }
        })
    }
    commit = (type, payload) => {
        this._mutations[type](payload)
    }
}

Action 类似于 mutation,不同在于:

  • Action 提交的是 mutation,而不是直接变更状态。
  • Action 可以包含任意异步操作

由于action可以执行异步函数,所以action在执行完成后应该返回一个Promise实例,对dispact的执行结果进行判断,如果不是一个Promise,需要给处理成一个promise。

class Store{
    constructor(options){
        // ...
        const store = this;

        store._actions = Object.create(null)

        const _actions = options.actions;
    
        forEachValue(_actions, (action, key)=>{
            store._actions[key] = (payload) => {
                const res = action.call(store, store.state, payload)
                if(!isPromise(res)){
                    return Promise.resolve(res)
                }
                return res
            }
        })
    }
    dispatch = (type, payload) => {
        this._actions[type](payload)
    }
}

useStore

通过inject来获取注册的store实例

import {inject} from 'vue'
export function useStore(injectKey = null) {
    return inject('store')
}

install

在Vue项目中使用Vuex,我们需要通过use()来注册store


const app = createApp({ /* 根组件 */ })

// 将 store 实例作为插件安装
app.use(store)

class Store{
    install(app, injectKey){ 
        // Vue3.x createApp().use(store)
        // 全局暴露一个 变量,暴露的是store的实例
        app.provide('store')

        // Vue2.x Vue.prototype.$store = this
        // 增添$store属性 使得可以直接在模板中使用 $store.state.count
        app.config.globalProperties.$store = this;
    }
}