{T}

概述

Vuex 采用单向数据流的设计理念,核心概念之间的关系如下:

图表渲染中…
核心概念职责是否可异步
State存储状态-
Getter派生状态-
Mutation修改状态❌ 必须同步
Action异步操作✅ 可异步

State

State 是 Vuex store 中存储应用状态的唯一数据源,是响应式的。

定义 State

javascript
const store = new Vuex.Store({
  state: {
    count: 0,
    user: {
      id: 1,
      name: '张三'
    },
    todos: [
      { id: 1, text: '学习 Vuex', done: true },
      { id: 2, text: '学习 Vue Router', done: false }
    ]
  }
})

在组件中获取 State

方式一:通过 this.$store

Vue SFC
<template>
  <div>
    <p>Count: {{ $store.state.count }}</p>
    <p>User: {{ $store.state.user.name }}</p>
  </div>
</template>

方式二:通过计算属性

Vue SFC
<script>
export default {
  computed: {
    count() {
      return this.$store.state.count
    },
    user() {
      return this.$store.state.user
    }
  }
}
</script>

方式三:使用 mapState 辅助函数

Vue SFC
<template>
  <div>
    <p>Count: {{ count }}</p>
    <p>User: {{ user.name }}</p>
  </div>
</template>

<script>
import { mapState } from 'vuex'

export default {
  computed: {
    // 对象展开运算符
    ...mapState(['count', 'user']),
    
    // 重命名
    ...mapState({
      currentCount: 'count',
      currentUser: 'user'
    }),
    
    // 使用函数获取
    ...mapState({
      countPlusLocalState(state) {
        return state.count + this.localCount
      }
    })
  }
}
</script>

mapState 使用方式对比

javascript
// 数组形式
...mapState(['count', 'user', 'todos'])

// 对象形式 - 重命名
...mapState({
  currentCount: 'count',
  currentUser: 'user'
})

// 对象形式 - 函数
...mapState({
  countWithLocal: state => state.count + this.localCount
})

Getter

Getter 类似于 Vue 的计算属性,用于从 store 中的 state 派生出一些状态,并且具有缓存特性。

定义 Getter

javascript
const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: '学习 Vuex', done: true },
      { id: 2, text: '学习 Vue Router', done: false }
    ]
  },
  getters: {
    // 获取完成的 todos
    doneTodos: state => {
      return state.todos.filter(todo => todo.done)
    },
    
    // 获取完成的 todos 数量
    doneTodosCount: (state, getters) => {
      return getters.doneTodos.length
    },
    
    // 通过 id 获取 todo
    getTodoById: state => id => {
      return state.todos.find(todo => todo.id === id)
    }
  }
})

在组件中使用 Getter

方式一:通过 this.$store

Vue SFC
<template>
  <div>
    <p>完成的任务数: {{ $store.getters.doneTodosCount }}</p>
  </div>
</template>

<script>
export default {
  computed: {
    doneTodos() {
      return this.$store.getters.doneTodos
    }
  }
}
</script>

方式二:使用 mapGetters 辅助函数

Vue SFC
<template>
  <div>
    <p>完成的任务数: {{ doneTodosCount }}</p>
    <p>完成的任务: {{ doneTodos }}</p>
  </div>
</template>

<script>
import { mapGetters } from 'vuex'

export default {
  computed: {
    // 数组形式
    ...mapGetters(['doneTodos', 'doneTodosCount']),
    
    // 对象形式 - 重命名
    ...mapGetters({
      finishedTodos: 'doneTodos',
      finishedCount: 'doneTodosCount'
    })
  }
}
</script>

Getter 返回函数

javascript
// store
getters: {
  getTodoById: state => id => {
    return state.todos.find(todo => todo.id === id)
  }
}

// 组件中使用
computed: {
  todo() {
    return this.$store.getters.getTodoById(1)
  }
}

Getter 与计算属性对比

特性Getter计算属性
缓存✅ 有✅ 有
作用域全局组件内
参数支持返回函数不支持
依赖依赖 state依赖组件 data

Mutation

Mutation 是更改 Vuex 的 store 中的状态的唯一方法,必须是同步函数。

定义 Mutation

javascript
const store = new Vuex.Store({
  state: {
    count: 0,
    user: null
  },
  mutations: {
    // 无参数
    increment(state) {
      state.count++
    },
    
    // 带参数(载荷)
    incrementBy(state, payload) {
      state.count += payload.amount
    },
    
    // 对象风格的提交方式
    setUser(state, payload) {
      state.user = payload
    },
    
    // 重置状态
    resetState(state) {
      state.count = 0
      state.user = null
    }
  }
})

提交 Mutation

方式一:普通提交

javascript
// 无参数
this.$store.commit('increment')

// 带参数
this.$store.commit('incrementBy', { amount: 10 })

// 对象风格提交
this.$store.commit({
  type: 'incrementBy',
  amount: 10
})

方式二:使用 mapMutations 辅助函数

Vue SFC
<template>
  <div>
    <button @click="increment">+1</button>
    <button @click="incrementBy({ amount: 10 })">+10</button>
  </div>
</template>

<script>
import { mapMutations } from 'vuex'

export default {
  methods: {
    // 数组形式
    ...mapMutations(['increment', 'incrementBy']),
    
    // 对象形式 - 重命名
    ...mapMutations({
      add: 'increment',
      addBy: 'incrementBy'
    })
  }
}
</script>

Mutation 必须是同步函数

javascript
// ❌ 错误 - 不要在 Mutation 中使用异步操作
mutations: {
  incrementAsync(state) {
    setTimeout(() => {
      state.count++  // 这会导致调试困难
    }, 1000)
  }
}

// ✅ 正确 - 异步操作应放在 Action 中
actions: {
  incrementAsync({ commit }) {
    setTimeout(() => {
      commit('increment')
    }, 1000)
  }
}

Mutation 命名规范

javascript
// 推荐使用常量定义 Mutation 类型
// store/mutation-types.js
export const INCREMENT = 'INCREMENT'
export const SET_USER = 'SET_USER'
export const RESET_STATE = 'RESET_STATE'

// store/index.js
import { INCREMENT, SET_USER, RESET_STATE } from './mutation-types'

const store = new Vuex.Store({
  mutations: {
    [INCREMENT](state) {
      state.count++
    },
    [SET_USER](state, user) {
      state.user = user
    },
    [RESET_STATE](state) {
      state.count = 0
      state.user = null
    }
  }
})

Action

Action 类似于 Mutation,但可以包含任意异步操作,通过提交 Mutation 来修改状态。

定义 Action

javascript
const store = new Vuex.Store({
  state: {
    count: 0,
    user: null,
    loading: false
  },
  mutations: {
    increment(state) {
      state.count++
    },
    setUser(state, user) {
      state.user = user
    },
    setLoading(state, loading) {
      state.loading = loading
    }
  },
  actions: {
    // 简单 Action
    increment({ commit }) {
      commit('increment')
    },
    
    // 异步 Action
    incrementAsync({ commit }) {
      setTimeout(() => {
        commit('increment')
      }, 1000)
    },
    
    // 带 Promise 的 Action
    fetchUser({ commit }, userId) {
      commit('setLoading', true)
      return fetch(`/api/users/${userId}`)
        .then(response => response.json())
        .then(user => {
          commit('setUser', user)
          commit('setLoading', false)
          return user
        })
        .catch(error => {
          commit('setLoading', false)
          throw error
        })
    },
    
    // 使用 async/await
    async fetchUserAsync({ commit }, userId) {
      commit('setLoading', true)
      try {
        const response = await fetch(`/api/users/${userId}`)
        const user = await response.json()
        commit('setUser', user)
        return user
      } finally {
        commit('setLoading', false)
      }
    },
    
    // 组合多个 Action
    async fetchUserAndPosts({ dispatch }, userId) {
      const user = await dispatch('fetchUserAsync', userId)
      await dispatch('fetchPosts', user.id)
    }
  }
})

分发 Action

方式一:通过 this.$store.dispatch

javascript
// 普通分发
this.$store.dispatch('increment')

// 带参数分发
this.$store.dispatch('incrementAsync')
this.$store.dispatch('fetchUser', 1)

// 对象风格分发
this.$store.dispatch({
  type: 'fetchUser',
  userId: 1
})

// 处理返回的 Promise
this.$store.dispatch('fetchUser', 1)
  .then(user => {
    console.log('获取用户成功:', user)
  })
  .catch(error => {
    console.error('获取用户失败:', error)
  })

// 使用 async/await
async function getUser() {
  try {
    const user = await this.$store.dispatch('fetchUser', 1)
    console.log('获取用户成功:', user)
  } catch (error) {
    console.error('获取用户失败:', error)
  }
}

方式二:使用 mapActions 辅助函数

Vue SFC
<template>
  <div>
    <button @click="increment">+1</button>
    <button @click="incrementAsync">异步+1</button>
    <button @click="fetchUser(1)">获取用户</button>
  </div>
</template>

<script>
import { mapActions } from 'vuex'

export default {
  methods: {
    // 数组形式
    ...mapActions(['increment', 'incrementAsync', 'fetchUser']),
    
    // 对象形式 - 重命名
    ...mapActions({
      add: 'increment',
      addAsync: 'incrementAsync',
      loadUser: 'fetchUser'
    })
  }
}
</script>

Action 与 Mutation 对比

特性MutationAction
是否可异步❌ 必须同步✅ 可异步
直接修改状态✅ 可以❌ 不可以
调用方式commitdispatch
返回值返回 Promise
调试追踪容易追踪需要配合工具

组合 Action

javascript
actions: {
  // 顺序执行
  async actionA({ commit }) {
    const data = await fetchData()
    commit('setData', data)
  },
  
  async actionB({ dispatch, commit }) {
    await dispatch('actionA')  // 等待 actionA 完成
    commit('doSomethingElse')
  },
  
  // 并行执行
  async fetchAllData({ dispatch }) {
    const [users, posts] = await Promise.all([
      dispatch('fetchUsers'),
      dispatch('fetchPosts')
    ])
    return { users, posts }
  }
}

辅助函数总结

mapState

javascript
import { mapState } from 'vuex'

export default {
  computed: {
    ...mapState(['count', 'user']),
    ...mapState({ currentCount: 'count' })
  }
}

mapGetters

javascript
import { mapGetters } from 'vuex'

export default {
  computed: {
    ...mapGetters(['doneTodos', 'doneTodosCount']),
    ...mapGetters({ finished: 'doneTodos' })
  }
}

mapMutations

javascript
import { mapMutations } from 'vuex'

export default {
  methods: {
    ...mapMutations(['increment', 'setUser']),
    ...mapMutations({ add: 'increment' })
  }
}

mapActions

javascript
import { mapActions } from 'vuex'

export default {
  methods: {
    ...mapActions(['fetchUser', 'incrementAsync']),
    ...mapActions({ loadUser: 'fetchUser' })
  }
}

最佳实践

1. 使用常量定义类型

javascript
// mutation-types.js
export const SET_USER = 'SET_USER'
export const SET_LOADING = 'SET_LOADING'
export const INCREMENT = 'INCREMENT'

// store.js
import * as types from './mutation-types'

mutations: {
  [types.SET_USER](state, user) {
    state.user = user
  }
}

2. Mutation 保持简单

javascript
// ❌ 不推荐 - 复杂逻辑
mutations: {
  updateUserProfile(state, { name, email, avatar }) {
    if (name) state.user.name = name
    if (email) state.user.email = email
    if (avatar) state.user.avatar = avatar
    state.user.updatedAt = new Date()
  }
}

// ✅ 推荐 - 简单直接
mutations: {
  setUser(state, user) {
    state.user = user
  }
}

3. Action 处理业务逻辑

javascript
actions: {
  async updateUserProfile({ commit, state }, updates) {
    commit('setLoading', true)
    try {
      const updatedUser = { ...state.user, ...updates, updatedAt: new Date() }
      await api.updateUser(updatedUser)
      commit('setUser', updatedUser)
    } finally {
      commit('setLoading', false)
    }
  }
}

4. 合理使用 Getter

javascript
// ✅ 推荐 - 需要计算或过滤时使用 Getter
getters: {
  activeUsers: state => state.users.filter(user => user.active),
  userCount: state => state.users.length
}

// ❌ 不推荐 - 简单的状态直接使用 State
getters: {
  users: state => state.users  // 多此一举
}

5. 组件中合理使用辅助函数

Vue SFC
<script>
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'

export default {
  computed: {
    // 优先使用 mapState 和 mapGetters
    ...mapState(['user', 'loading']),
    ...mapGetters(['isLoggedIn', 'userName']),
    
    // 组件内部计算属性
    localComputed() {
      return this.user.name.toUpperCase()
    }
  },
  methods: {
    // 优先使用 mapMutations 和 mapActions
    ...mapMutations(['setUser']),
    ...mapActions(['fetchUser', 'logout']),
    
    // 组件内部方法
    handleClick() {
      this.fetchUser(this.userId)
    }
  }
}
</script>