Skip to content

Vuex 状态管理深度解析

Vuex是Vue.js的官方状态管理库,采用集中式存储管理应用的所有组件状态,并以相应的规则保证状态以可预测的方式发生变化。

🎯 Vuex核心概念

mermaid
graph TB
    A[Vue Components] --> B[Actions]
    B --> C[Mutations]
    C --> D[State]
    D --> A
    
    E[Backend API] --> B
    F[Devtools] --> C
    
    subgraph "Vuex Store"
        D
        C
        B
        G[Getters]
    end
    
    D --> G
    G --> A

核心组成部分

  • State:单一状态树,存储应用的所有状态
  • Getters:从state派生的状态,类似计算属性
  • Mutations:同步修改state的唯一方式
  • Actions:异步操作,提交mutations
  • Modules:模块化管理,避免单一状态树过于庞大

🏗️ Vuex源码实现原理

1. Store类核心实现

javascript
class Store {
  constructor(options = {}) {
    const {
      plugins = [],
      strict = false
    } = options

    // 内部状态
    this._committing = false
    this._actions = Object.create(null)
    this._actionSubscribers = []
    this._mutations = Object.create(null)
    this._wrappedGetters = Object.create(null)
    this._modules = new ModuleCollection(options)
    this._modulesNamespaceMap = Object.create(null)
    this._subscribers = []
    this._watcherVM = new Vue()
    this._makeLocalGettersCache = Object.create(null)

    // 绑定commit和dispatch的this指向
    const store = this
    const { dispatch, commit } = this
    this.dispatch = function boundDispatch(type, payload) {
      return dispatch.call(store, type, payload)
    }
    this.commit = function boundCommit(type, payload) {
      return commit.call(store, type, payload)
    }

    // 严格模式
    this.strict = strict

    const state = this._modules.root.state

    // 初始化根模块,递归注册所有子模块
    installModule(this, state, [], this._modules.root)

    // 初始化store vm,使state和getters响应式
    resetStoreVM(this, state)

    // 应用插件
    plugins.forEach(plugin => plugin(this))
  }

  get state() {
    return this._vm._data.$$state
  }

  set state(v) {
    if (process.env.NODE_ENV !== 'production') {
      assert(false, `use store.replaceState() to explicit replace store state.`)
    }
  }

  commit(_type, _payload, _options) {
    // 统一格式
    const { type, payload, options } = unifyObjectStyle(_type, _payload, _options)

    const mutation = { type, payload }
    const entry = this._mutations[type]
    
    if (!entry) {
      if (process.env.NODE_ENV !== 'production') {
        console.error(`[vuex] unknown mutation type: ${type}`)
      }
      return
    }

    // 执行mutation
    this._withCommit(() => {
      entry.forEach(function commitIterator(handler) {
        handler(payload)
      })
    })

    // 通知订阅者
    this._subscribers
      .slice()
      .forEach(sub => sub(mutation, this.state))

    if (process.env.NODE_ENV !== 'production' && options && options.silent) {
      console.warn(
        `[vuex] mutation type: ${type}. Silent option has been removed. ` +
        'Use the filter functionality in the vue-devtools'
      )
    }
  }

  dispatch(_type, _payload) {
    // 统一格式
    const { type, payload } = unifyObjectStyle(_type, _payload)

    const action = { type, payload }
    const entry = this._actions[type]
    
    if (!entry) {
      if (process.env.NODE_ENV !== 'production') {
        console.error(`[vuex] unknown action type: ${type}`)
      }
      return
    }

    try {
      this._actionSubscribers
        .slice()
        .filter(sub => sub.before)
        .forEach(sub => sub.before(action, this.state))
    } catch (e) {
      if (process.env.NODE_ENV !== 'production') {
        console.warn(`[vuex] error in before action subscribers: `)
        console.error(e)
      }
    }

    const result = entry.length > 1
      ? Promise.all(entry.map(handler => handler(payload)))
      : entry[0](payload)

    return new Promise((resolve, reject) => {
      result.then(res => {
        try {
          this._actionSubscribers
            .filter(sub => sub.after)
            .forEach(sub => sub.after(action, this.state))
        } catch (e) {
          if (process.env.NODE_ENV !== 'production') {
            console.warn(`[vuex] error in after action subscribers: `)
            console.error(e)
          }
        }
        resolve(res)
      }, error => {
        try {
          this._actionSubscribers
            .filter(sub => sub.error)
            .forEach(sub => sub.error(action, this.state, error))
        } catch (e) {
          if (process.env.NODE_ENV !== 'production') {
            console.warn(`[vuex] error in error action subscribers: `)
            console.error(e)
          }
        }
        reject(error)
      })
    })
  }

  // 确保只能通过mutation修改state
  _withCommit(fn) {
    const committing = this._committing
    this._committing = true
    fn()
    this._committing = committing
  }
}

2. 响应式实现

javascript
function resetStoreVM(store, state, hot) {
  const oldVm = store._vm

  // 绑定store的getters
  store.getters = {}
  // 重置本地getters缓存
  store._makeLocalGettersCache = Object.create(null)
  const wrappedGetters = store._wrappedGetters
  const computed = {}
  
  forEachValue(wrappedGetters, (fn, key) => {
    // 使用computed来利用其缓存特性
    computed[key] = partial(fn, store)
    Object.defineProperty(store.getters, key, {
      get: () => store._vm[key],
      enumerable: true
    })
  })

  // 使用Vue实例来保存state和getters
  const silent = Vue.config.silent
  Vue.config.silent = true
  store._vm = new Vue({
    data: {
      $$state: state
    },
    computed
  })
  Vue.config.silent = silent

  // 启用严格模式
  if (store.strict) {
    enableStrictMode(store)
  }

  if (oldVm) {
    if (hot) {
      // 热重载时保持现有状态
      store._withCommit(() => {
        oldVm._data.$$state = null
      })
    }
    Vue.nextTick(() => oldVm.$destroy())
  }
}

function enableStrictMode(store) {
  store._vm.$watch(function () { return this._data.$$state }, () => {
    if (process.env.NODE_ENV !== 'production') {
      assert(store._committing, `do not mutate vuex store state outside mutation handlers.`)
    }
  }, { deep: true, sync: true })
}

3. 模块系统实现

javascript
class ModuleCollection {
  constructor(rawRootModule) {
    // 注册根模块
    this.register([], rawRootModule, false)
  }

  get(path) {
    return path.reduce((module, key) => {
      return module.getChild(key)
    }, this.root)
  }

  getNamespace(path) {
    let module = this.root
    return path.reduce((namespace, key) => {
      module = module.getChild(key)
      return namespace + (module.namespaced ? key + '/' : '')
    }, '')
  }

  register(path, rawModule, runtime = true) {
    if (process.env.NODE_ENV !== 'production') {
      assertRawModule(path, rawModule)
    }

    const newModule = new Module(rawModule, runtime)
    if (path.length === 0) {
      this.root = newModule
    } else {
      const parent = this.get(path.slice(0, -1))
      parent.addChild(path[path.length - 1], newModule)
    }

    // 注册嵌套模块
    if (rawModule.modules) {
      forEachValue(rawModule.modules, (rawChildModule, key) => {
        this.register(path.concat(key), rawChildModule, runtime)
      })
    }
  }
}

class Module {
  constructor(rawModule, runtime) {
    this.runtime = runtime
    this._children = Object.create(null)
    this._rawModule = rawModule
    const rawState = rawModule.state

    this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
  }

  get namespaced() {
    return !!this._rawModule.namespaced
  }

  addChild(key, module) {
    this._children[key] = module
  }

  removeChild(key) {
    delete this._children[key]
  }

  getChild(key) {
    return this._children[key]
  }

  hasChild(key) {
    return key in this._children
  }

  update(rawModule) {
    this._rawModule.namespaced = rawModule.namespaced
    if (rawModule.actions) {
      this._rawModule.actions = rawModule.actions
    }
    if (rawModule.mutations) {
      this._rawModule.mutations = rawModule.mutations
    }
    if (rawModule.getters) {
      this._rawModule.getters = rawModule.getters
    }
  }

  forEachChild(fn) {
    forEachValue(this._children, fn)
  }

  forEachGetter(fn) {
    if (this._rawModule.getters) {
      forEachValue(this._rawModule.getters, fn)
    }
  }

  forEachAction(fn) {
    if (this._rawModule.actions) {
      forEachValue(this._rawModule.actions, fn)
    }
  }

  forEachMutation(fn) {
    if (this._rawModule.mutations) {
      forEachValue(this._rawModule.mutations, fn)
    }
  }
}

🔧 高级用法与最佳实践

1. 模块化设计

javascript
// store/modules/user.js
const state = {
  profile: null,
  permissions: [],
  preferences: {}
}

const getters = {
  isLoggedIn: state => !!state.profile,
  hasPermission: state => permission => {
    return state.permissions.includes(permission)
  },
  fullName: state => {
    return state.profile ? `${state.profile.firstName} ${state.profile.lastName}` : ''
  }
}

const mutations = {
  SET_PROFILE(state, profile) {
    state.profile = profile
  },
  SET_PERMISSIONS(state, permissions) {
    state.permissions = permissions
  },
  UPDATE_PREFERENCES(state, preferences) {
    state.preferences = { ...state.preferences, ...preferences }
  },
  CLEAR_USER_DATA(state) {
    state.profile = null
    state.permissions = []
    state.preferences = {}
  }
}

const actions = {
  async login({ commit, dispatch }, credentials) {
    try {
      const response = await api.login(credentials)
      const { user, token, permissions } = response.data
      
      // 存储token
      localStorage.setItem('token', token)
      
      // 更新状态
      commit('SET_PROFILE', user)
      commit('SET_PERMISSIONS', permissions)
      
      // 获取用户偏好设置
      await dispatch('fetchPreferences')
      
      return user
    } catch (error) {
      throw error
    }
  },

  async logout({ commit }) {
    try {
      await api.logout()
    } finally {
      localStorage.removeItem('token')
      commit('CLEAR_USER_DATA')
    }
  },

  async fetchPreferences({ commit, state }) {
    if (!state.profile) return
    
    try {
      const response = await api.getUserPreferences(state.profile.id)
      commit('UPDATE_PREFERENCES', response.data)
    } catch (error) {
      console.error('Failed to fetch user preferences:', error)
    }
  }
}

export default {
  namespaced: true,
  state,
  getters,
  mutations,
  actions
}

2. 插件系统

javascript
// 持久化插件
const persistencePlugin = store => {
  // 初始化时从localStorage恢复状态
  const savedState = localStorage.getItem('vuex-state')
  if (savedState) {
    store.replaceState(JSON.parse(savedState))
  }

  // 监听mutation,保存状态到localStorage
  store.subscribe((mutation, state) => {
    // 只保存特定模块的状态
    const stateToSave = {
      user: state.user,
      settings: state.settings
    }
    localStorage.setItem('vuex-state', JSON.stringify(stateToSave))
  })
}

// 日志插件
const loggerPlugin = store => {
  store.subscribe((mutation, state) => {
    console.group(`Mutation: ${mutation.type}`)
    console.log('Payload:', mutation.payload)
    console.log('State after:', state)
    console.groupEnd()
  })

  store.subscribeAction((action, state) => {
    console.group(`Action: ${action.type}`)
    console.log('Payload:', action.payload)
    console.log('State before:', state)
    console.groupEnd()
  })
}

// 使用插件
const store = new Vuex.Store({
  // ...
  plugins: [persistencePlugin, loggerPlugin]
})

3. 严格模式与开发工具

javascript
const store = new Vuex.Store({
  // 开发环境启用严格模式
  strict: process.env.NODE_ENV !== 'production',
  
  // 开发工具支持
  devtools: process.env.NODE_ENV !== 'production',
  
  plugins: process.env.NODE_ENV !== 'production' 
    ? [createLogger()] 
    : []
})

🎯 性能优化技巧

1. 合理使用getters

javascript
// ❌ 避免在getters中进行昂贵计算
const getters = {
  expensiveGetter: state => {
    // 每次访问都会重新计算
    return state.items.map(item => {
      // 昂贵的计算
      return processItem(item)
    })
  }
}

// ✅ 使用缓存优化
const getters = {
  expensiveGetter: state => {
    // 利用Vue的计算属性缓存
    return state.processedItems
  }
}

const mutations = {
  SET_ITEMS(state, items) {
    state.items = items
    // 预处理数据
    state.processedItems = items.map(processItem)
  }
}

2. 避免频繁的状态变更

javascript
// ❌ 频繁的单个mutation
actions: {
  updateMultipleFields({ commit }, data) {
    commit('SET_FIELD_A', data.a)
    commit('SET_FIELD_B', data.b)
    commit('SET_FIELD_C', data.c)
  }
}

// ✅ 批量更新
mutations: {
  UPDATE_MULTIPLE_FIELDS(state, data) {
    Object.assign(state, data)
  }
}

actions: {
  updateMultipleFields({ commit }, data) {
    commit('UPDATE_MULTIPLE_FIELDS', data)
  }
}

3. 模块懒加载

javascript
// 动态注册模块
const store = new Vuex.Store({
  modules: {
    // 核心模块
    user: userModule
  }
})

// 路由级别的模块懒加载
router.beforeEach(async (to, from, next) => {
  if (to.matched.some(record => record.meta.requiresAuth)) {
    if (!store.hasModule('admin')) {
      const adminModule = await import('./store/modules/admin')
      store.registerModule('admin', adminModule.default)
    }
  }
  next()
})

Vuex通过其严格的状态管理模式和强大的开发工具支持,为大型Vue应用提供了可预测的状态管理解决方案。