Vue 3源码深度解析之:`Vuex 4.0`:`Store`的内部实现与`Composition API`的集成。

各位靓仔靓女,晚上好!我是今晚的讲师,咱们今天来聊聊 Vuex 4.0 的内部实现,以及它和 Composition API 如何“眉来眼去”的集成在一起。

别被“源码深度解析”吓到,咱们不搞那种让你看了就想睡觉的枯燥分析,争取用大白话把 Vuex 的核心扒个精光。

一、Vuex 4.0:不再是大哥大,而是瑞士军刀

Vuex,作为 Vue 的官方状态管理库,在 Vue 2 时代,它就像个大哥大,功能强大但有点笨重。到了 Vue 3 时代,它摇身一变,成了瑞士军刀,轻巧灵活,功能还更丰富了。

那么,Vuex 4.0 到底做了哪些改变呢?

  • 拥抱 Composition API: 这是最大的变化。Vuex 4.0 提供了 useStore hook,让你可以像使用 refreactive 一样轻松地访问和修改状态。
  • TypeScript 支持: 告别 any 地狱,类型检查让你的代码更健壮。
  • 模块化增强: 模块化更加灵活,可以动态注册和卸载模块。
  • 更小的体积: 性能优化,让你的应用跑得更快。

二、Store 的内部实现:揭开神秘面纱

Store 是 Vuex 的核心,所有的状态、 mutations、actions 和 getters 都存储在其中。 让我们一起看看它的内部构造。

// Vuex 4.0 的 Store 类
class Store<S> {
  constructor(options: StoreOptions<S>) {
    const {
      state,
      mutations,
      actions,
      getters,
      modules,
      plugins,
      strict = false
    } = options;

    // 1. 状态:如果是函数,则执行获取初始状态
    this._state = reactive({
      data: typeof state === 'function' ? (state as Function)() : state
    });

    // 2. 存储 mutations, actions 和 getters
    this._mutations = Object.create(null);
    this._actions = Object.create(null);
    this._wrappedGetters = Object.create(null);

    // 3. 模块收集与注册
    this._modules = new ModuleCollection(options);
    installModule(this, this._state, [], this._modules.root);

    // 4. 注册 plugins
    if (plugins) {
      plugins.forEach(plugin => plugin(this));
    }

    // 5. 严格模式
    this.strict = strict;

    // 6. 如果是开发环境,则开启严格模式的监听
    if (process.env.NODE_ENV !== 'production' && strict) {
      enableStrictMode(this);
    }

    // 7. 初始化完成标志
    this._committing = false;
    this._subscribers = [];
    this._watcherVM = new Vue(); // Vue2 实现的观察者,用于订阅和触发事件
  }

  // 获取 state
  get state(): S {
    return this._state.data;
  }

  // 提交 mutation
  commit = (type: string, payload?: any, options?: CommitOptions) => {
    // ... 省略 commit 的实现细节,后面会详细讲解
  }

  // 触发 action
  dispatch = (type: string, payload?: any) => {
    // ... 省略 dispatch 的实现细节,后面会详细讲解
  }

  // 注册 mutation
  registerMutation(type: string, handler: Function, local: boolean = false) {
    // ... 省略注册 mutation 的实现细节
  }

  // 注册 action
  registerAction(type: string, handler: Function, local: boolean = false) {
    // ... 省略注册 action 的实现细节
  }

  // 注册 getter
  registerGetter(type: string, getter: Function, local: boolean = false) {
    // ... 省略注册 getter 的实现细节
  }

  // 替换 state
  replaceState(state: S) {
    // ... 省略替换 state 的实现细节
  }

  // 订阅 mutation
  subscribe(fn: Function): Function {
    // ... 省略订阅 mutation 的实现细节
  }
}

export function createStore<S>(options: StoreOptions<S>): Store<S> {
  return new Store<S>(options);
}

我们来逐行解读一下:

  1. constructor(options: StoreOptions<S>) Store 类的构造函数,接收一个 options 对象,包含了 statemutationsactionsgettersmodulespluginsstrict 等配置。
  2. this._state = reactive({ data: ... }) 使用 Vue 3 的 reactive 函数将 state 包装成一个响应式对象。这意味着当 state 的值发生变化时,所有依赖它的组件都会自动更新。如果state是一个函数,则调用该函数返回初始状态。
  3. this._mutations = Object.create(null)this._actions = Object.create(null)this._wrappedGetters = Object.create(null) 创建三个对象,分别用于存储 mutationsactionsgetters。使用 Object.create(null) 可以创建一个没有原型链的对象,避免不必要的属性查找。
  4. this._modules = new ModuleCollection(options)installModule(this, this._state, [], this._modules.root) 这是 Vuex 模块化的核心。ModuleCollection 用于收集所有的模块,并构建一个模块树。installModule 函数则负责将模块的状态、mutations、actions 和 getters 注册到 Store 实例中。
  5. plugins.forEach(plugin => plugin(this)) 注册插件。插件可以用来扩展 Vuex 的功能,例如日志记录、数据持久化等。
  6. this.strict = strict 是否开启严格模式。在严格模式下,只能通过 mutation 来修改 state,否则会抛出错误。
  7. enableStrictMode(this) 开启严格模式的监听。
  8. this.commit = (type: string, payload?: any, options?: CommitOptions) => { ... } commit 函数用于提交 mutation。它会查找对应的 mutation,并执行它。
  9. this.dispatch = (type: string, payload?: any) => { ... } dispatch 函数用于触发 action。它会查找对应的 action,并执行它。

三、commitdispatch:状态管理的发动机

commitdispatch 是 Vuex 中最重要的两个函数,它们是状态管理的发动机。

  • commit:同步修改状态

    commit 用于提交 mutationmutation 是唯一允许修改 state 的方式。

    commit = (type: string, payload?: any, options?: CommitOptions) => {
      // 1. 查找对应的 mutation
      const mutation = this._mutations[type];
      if (!mutation) {
        if (process.env.NODE_ENV !== 'production') {
          console.error(`[vuex] unknown mutation type: ${type}`);
        }
        return;
      }
    
      // 2. 执行 mutation
      try {
        this._committing = true; // 标记当前正在提交 mutation
        mutation.forEach(handler => handler(this._state.data, payload));
      } catch (e) {
        throw e;
      } finally {
        this._committing = false; // 标记 mutation 提交完成
      }
    
      // 3. 触发订阅者
      this._subscribers.forEach(sub => sub({ type, payload }, this.state));
    }
    • 查找 mutation 根据 type 查找对应的 mutation
    • 执行 mutation 遍历所有的 mutation 处理函数,并依次执行它们。在执行 mutation 期间,会设置 this._committing = true,防止在 mutation 中直接修改 state
    • 触发订阅者: 遍历所有的订阅者,并依次执行它们。订阅者可以用来监听 mutation 的提交,例如记录日志、更新 UI 等。
  • dispatch:异步触发 action

    dispatch 用于触发 actionaction 可以包含任意的异步操作。

    dispatch = (type: string, payload?: any) => {
      // 1. 查找对应的 action
      const action = this._actions[type];
      if (!action) {
        if (process.env.NODE_ENV !== 'production') {
          console.error(`[vuex] unknown action type: ${type}`);
        }
        return Promise.reject(new Error(`unknown action type: ${type}`));
      }
    
      // 2. 执行 action
      try {
        return action.length > 1
          ? Promise.all(action.map(handler => handler({
              dispatch: this.dispatch,
              commit: this.commit,
              state: this.state,
              getters: this.getters,
              rootState: this.state,
              rootGetters: this.getters
            }, payload)))
          : action[0]({
              dispatch: this.dispatch,
              commit: this.commit,
              state: this.state,
              getters: this.getters,
              rootState: this.state,
              rootGetters: this.getters
            }, payload)
      } catch (e) {
        return Promise.reject(e);
      }
    }
    • 查找 action 根据 type 查找对应的 action
    • 执行 action 执行 action 处理函数,并将 context 对象和 payload 作为参数传递给它。context 对象包含了 dispatchcommitstategettersrootStaterootGetters 等属性。action 可以通过 context.commit 提交 mutation,也可以通过 context.dispatch 触发其他的 action

四、ModuleCollectioninstallModule:模块化的基石

Vuex 的模块化允许你将 store 分割成多个模块,每个模块拥有自己的 statemutationsactionsgetters

  • ModuleCollection:收集模块

    ModuleCollection 用于收集所有的模块,并构建一个模块树。

    class ModuleCollection {
      constructor(rawRootModule: RawModule, runtime: boolean) {
        // register root module (Vuex.Store options)
        this.register([rawRootModule], runtime)
      }
    
      get(path: string[]): Module {
        return path.reduce((module, key) => {
          return module.getChild(key)
        }, this.root)
      }
    
      getNamespace(path: string[]): string {
        let module = this.root
        return path.reduce((namespace, key) => {
          module = module.getChild(key)
          return namespace + (module.namespaced ? key + '/' : '')
        }, '')
      }
    
      update(rawRootModule: RawModule) {
        update(this.root, rawRootModule)
      }
    
      register(path: string[], rawModule: RawModule, runtime: boolean = true) {
        const parent = this.get(path.slice(0, -1))
        const newModule = new Module(rawModule, runtime)
        parent.addChild(path[path.length - 1], newModule)
    
        // recursively register all submodules
        if (rawModule.modules) {
          forEachValue(rawModule.modules, (rawChildModule, key) => {
            this.register(path.concat(key), rawChildModule, runtime)
          })
        }
      }
    
      unregister(path: string[]) {
        const parent = this.get(path.slice(0, -1))
        const key = path[path.length - 1]
        if (!parent.getChild(key).runtime) return
    
        parent.removeChild(key)
      }
    }
    • register(path: string[], rawModule: RawModule, runtime: boolean = true) 注册一个模块。它会创建一个 Module 实例,并将其添加到模块树中。如果模块包含子模块,则递归注册所有的子模块。
    • get(path: string[]): Module 根据路径获取模块。
    • getNamespace(path: string[]): string 根据路径获取模块的命名空间。
  • installModule:安装模块

    installModule 函数负责将模块的状态、mutations、actions 和 getters 注册到 Store 实例中。

    function installModule(store: Store<any>, rootState: any, path: string[], module: Module, hot: boolean = false) {
      const isRoot = !path.length
      const namespace = store._modules.getNamespace(path)
    
      // register in namespace map
      if (module.namespaced) {
        if (store._modulesNamespaceMap[namespace] && process.env.NODE_ENV !== 'production') {
          console.error(`[vuex] duplicate namespace ${namespace} for the namespaced module at ${path.join('/')}`)
        }
        store._modulesNamespaceMap[namespace] = module
      }
    
      // set state
      if (!isRoot && !hot) {
        const parentState = getNestedState(rootState, path.slice(0, -1))
        const moduleName = path[path.length - 1]
        store._withCommit(() => {
          parentState[moduleName] = module.state
        })
      }
    
      const local = module.context = makeLocalContext(store, namespace, path)
    
      module.forEachMutation((mutation, key) => {
        const namespacedType = namespace + key
        registerMutation(store, namespacedType, mutation, local)
      })
    
      module.forEachAction((action, key) => {
        const type = action.root ? key : namespace + key
        const handler = action.handler || action
        registerAction(store, type, handler, local)
      })
    
      module.forEachGetter((getter, key) => {
        const namespacedType = namespace + key
        registerGetter(store, namespacedType, getter, local)
      })
    
      // install submodules
      module.forEachChild((child, key) => {
        installModule(store, rootState, path.concat(key), child, hot)
      })
    
      // watch state if hot
      if (hot && module.hotUpdate) {
        module.hotUpdate(store, local)
      }
    }
    • 获取模块的命名空间: 使用 store._modules.getNamespace(path) 获取模块的命名空间。
    • 设置模块的状态: 将模块的状态添加到父模块的状态中。
    • 创建模块的本地上下文: 使用 makeLocalContext 函数创建模块的本地上下文。本地上下文包含了 dispatchcommitstategettersrootState 等属性,但是它们只作用于当前模块。
    • 注册 mutationactiongetter 遍历模块的 mutationactiongetter,并将它们注册到 Store 实例中。
    • 递归安装子模块: 递归安装所有的子模块。

五、useStore:拥抱 Composition API

Vuex 4.0 提供了 useStore hook,让你可以像使用 refreactive 一样轻松地访问和修改状态。

import { inject } from 'vue'
import { storeKey } from './injectKey'

export function useStore(key?: string): Store<any> {
  return inject(key !== null && key !== void 0 ? key : storeKey)
}

useStore 函数实际上就是一个 inject 函数,它从 Vue 的依赖注入系统中获取 Store 实例。

使用 useStore 非常简单:

<template>
  <div>
    <h1>{{ count }}</h1>
    <button @click="increment">Increment</button>
  </div>
</template>

<script setup>
import { useStore } from 'vuex'
import { computed } from 'vue'

const store = useStore()

const count = computed(() => store.state.count)

const increment = () => {
  store.commit('increment')
}
</script>
  • const store = useStore() 获取 Store 实例。
  • const count = computed(() => store.state.count) 使用 computed 函数创建一个计算属性,用于监听 state.count 的变化。
  • const increment = () => { store.commit('increment') } 使用 store.commit 提交 mutation,修改 state.count 的值。

六、Vuex 4.0 与 Composition API 的集成:天作之合

Vuex 4.0 与 Composition API 的集成,可以说是天作之合。它们可以很好地协同工作,让你的代码更加简洁、易于维护。

  • 更简洁的代码: 使用 useStore hook,你可以直接在组件中使用 statemutationsactions,无需再使用 mapStatemapMutationsmapActions 等辅助函数。

  • 更好的类型检查: Vuex 4.0 提供了 TypeScript 支持,你可以使用类型检查来确保你的代码的正确性。

  • 更灵活的组合: 使用 Composition API,你可以将不同的状态管理逻辑组合在一起,创建更复杂的功能。

七、实战演练:一个简单的计数器

让我们用 Vuex 4.0 和 Composition API 来实现一个简单的计数器。

// store.ts
import { createStore } from 'vuex'

export default createStore({
  state: {
    count: 0
  },
  mutations: {
    increment(state) {
      state.count++
    },
    decrement(state) {
      state.count--
    }
  },
  actions: {
    incrementAsync(context) {
      setTimeout(() => {
        context.commit('increment')
      }, 1000)
    }
  },
  getters: {
    doubleCount(state) {
      return state.count * 2
    }
  }
})
// Counter.vue
<template>
  <div>
    <h1>{{ count }}</h1>
    <p>Double Count: {{ doubleCount }}</p>
    <button @click="increment">Increment</button>
    <button @click="decrement">Decrement</button>
    <button @click="incrementAsync">Increment Async</button>
  </div>
</template>

<script setup>
import { useStore } from 'vuex'
import { computed } from 'vue'

const store = useStore()

const count = computed(() => store.state.count)
const doubleCount = computed(() => store.getters.doubleCount)

const increment = () => {
  store.commit('increment')
}

const decrement = () => {
  store.commit('decrement')
}

const incrementAsync = () => {
  store.dispatch('incrementAsync')
}
</script>

这个例子很简单,但是它可以让你看到 Vuex 4.0 和 Composition API 的强大之处。

八、总结

今天,我们深入了解了 Vuex 4.0 的内部实现,以及它和 Composition API 如何集成在一起。希望通过这次讲座,你对 Vuex 有了更深入的理解,并能够在实际项目中更好地使用它。

记住,源码分析不是目的,目的是更好地理解框架的原理,从而写出更高效、更健壮的代码。

好了,今天的讲座就到这里,大家可以自由提问。如果暂时没有问题,不妨回去多敲几行代码,实践出真知嘛! 各位晚安!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注