Skip to content

Vue 状态管理 ​

Vuex ​

核心概念 ​

Vuex 是一个专为 Vue.js 开发的状态管理模式,采用集中式存储管理应用的所有组件状态。

            Vue 组件
         ↗          ↘
  Actions  ←→  Mutations  →  State  →  Vue 组件
            Vue 组件
         ↗          ↘
  Actions  ←→  Mutations  →  State  →  Vue 组件
  • State — 数据源
  • Getters — 计算属性(派生状态)
  • Mutations — 同步修改 State(唯一途径)
  • Actions — 异步操作,提交 Mutations
  • Modules — 模块化

安装与配置(Vue2) ​

js
// src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    count: 0,
    user: null,
    list: []
  },
  getters: {
    doubleCount: state => state.count * 2,
    isLogin: state => !!state.user
  },
  mutations: {
    SET_COUNT(state, payload) {
      state.count = payload
    },
    SET_USER(state, user) {
      state.user = user
    }
  },
  actions: {
    async fetchUser({ commit }, id) {
      const res = await fetch(`/api/user/${id}`).then(r => r.json())
      commit('SET_USER', res)
    }
  }
})
// src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    count: 0,
    user: null,
    list: []
  },
  getters: {
    doubleCount: state => state.count * 2,
    isLogin: state => !!state.user
  },
  mutations: {
    SET_COUNT(state, payload) {
      state.count = payload
    },
    SET_USER(state, user) {
      state.user = user
    }
  },
  actions: {
    async fetchUser({ commit }, id) {
      const res = await fetch(`/api/user/${id}`).then(r => r.json())
      commit('SET_USER', res)
    }
  }
})
js
// main.js
import store from './store'

new Vue({ store }).$mount('#app')
// main.js
import store from './store'

new Vue({ store }).$mount('#app')

State — 状态 ​

js
// 获取 state
this.$store.state.count

// 辅助函数
import { mapState } from 'vuex'

export default {
  computed: {
    ...mapState(['count', 'user']),
    // 重命名
    ...mapState({ num: 'count' }),
    // 函数形式
    ...mapState({ doubleNum: state => state.count * 2 })
  }
}
// 获取 state
this.$store.state.count

// 辅助函数
import { mapState } from 'vuex'

export default {
  computed: {
    ...mapState(['count', 'user']),
    // 重命名
    ...mapState({ num: 'count' }),
    // 函数形式
    ...mapState({ doubleNum: state => state.count * 2 })
  }
}

Getters — 计算属性 ​

js
// store
getters: {
  doneTodos: state => state.todos.filter(t => t.done),
  todoById: state => id => state.todos.find(t => t.id === id)
}
// store
getters: {
  doneTodos: state => state.todos.filter(t => t.done),
  todoById: state => id => state.todos.find(t => t.id === id)
}
js
// 组件中使用
import { mapGetters } from 'vuex'

export default {
  computed: {
    ...mapGetters(['doneTodos', 'todoById'])
  }
}
// 组件中使用
import { mapGetters } from 'vuex'

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

Mutations — 同步修改 ​

js
// store
mutations: {
  INCREMENT(state, payload = 1) {
    state.count += payload
  },
  UPDATE_USER(state, user) {
    state.user = { ...state.user, ...user }
  }
}
// store
mutations: {
  INCREMENT(state, payload = 1) {
    state.count += payload
  },
  UPDATE_USER(state, user) {
    state.user = { ...state.user, ...user }
  }
}
js
// 提交 mutation
this.$store.commit('INCREMENT', 10)
this.$store.commit({ type: 'INCREMENT', payload: 10 })

// 辅助函数
import { mapMutations } from 'vuex'

export default {
  methods: {
    ...mapMutations(['INCREMENT', 'UPDATE_USER']),
    ...mapMutations({ add: 'INCREMENT' })
  }
}
// 提交 mutation
this.$store.commit('INCREMENT', 10)
this.$store.commit({ type: 'INCREMENT', payload: 10 })

// 辅助函数
import { mapMutations } from 'vuex'

export default {
  methods: {
    ...mapMutations(['INCREMENT', 'UPDATE_USER']),
    ...mapMutations({ add: 'INCREMENT' })
  }
}

Mutation 必须是同步函数。 异步操作需要放在 Action 中。

Actions — 异步操作 ​

js
// store
actions: {
  // context 包含 commit / dispatch / state / getters
  async fetchData({ commit, state }, params) {
    commit('SET_LOADING', true)
    try {
      const res = await api.getList(params)
      commit('SET_LIST', res.data)
    } catch (err) {
      commit('SET_ERROR', err)
    } finally {
      commit('SET_LOADING', false)
    }
  }
}
// store
actions: {
  // context 包含 commit / dispatch / state / getters
  async fetchData({ commit, state }, params) {
    commit('SET_LOADING', true)
    try {
      const res = await api.getList(params)
      commit('SET_LIST', res.data)
    } catch (err) {
      commit('SET_ERROR', err)
    } finally {
      commit('SET_LOADING', false)
    }
  }
}
js
// 分发 action
this.$store.dispatch('fetchData', { page: 1 })
this.$store.dispatch({ type: 'fetchData', page: 1 })

// 辅助函数
import { mapActions } from 'vuex'

export default {
  methods: {
    ...mapActions(['fetchData']),
    ...mapActions({ load: 'fetchData' })
  }
}
// 分发 action
this.$store.dispatch('fetchData', { page: 1 })
this.$store.dispatch({ type: 'fetchData', page: 1 })

// 辅助函数
import { mapActions } from 'vuex'

export default {
  methods: {
    ...mapActions(['fetchData']),
    ...mapActions({ load: 'fetchData' })
  }
}

Modules — 模块化 ​

js
// src/store/modules/user.js
export default {
  namespaced: true, // 开启命名空间
  state: () => ({
    name: '',
    token: ''
  }),
  getters: {
    isLogin: state => !!state.token
  },
  mutations: {
    SET_TOKEN(state, token) {
      state.token = token
    }
  },
  actions: {
    login({ commit }, credentials) {
      const { token } = await api.login(credentials)
      commit('SET_TOKEN', token)
    }
  }
}
// src/store/modules/user.js
export default {
  namespaced: true, // 开启命名空间
  state: () => ({
    name: '',
    token: ''
  }),
  getters: {
    isLogin: state => !!state.token
  },
  mutations: {
    SET_TOKEN(state, token) {
      state.token = token
    }
  },
  actions: {
    login({ commit }, credentials) {
      const { token } = await api.login(credentials)
      commit('SET_TOKEN', token)
    }
  }
}
js
// src/store/index.js
import user from './modules/user'
import cart from './modules/cart'

export default new Vuex.Store({
  modules: {
    user,
    cart
  }
})
// src/store/index.js
import user from './modules/user'
import cart from './modules/cart'

export default new Vuex.Store({
  modules: {
    user,
    cart
  }
})
js
// 组件中访问模块 state
this.$store.state.user.token

// 命名空间后调用
this.$store.commit('user/SET_TOKEN', 'xxx')
this.$store.dispatch('user/login', data)

// 辅助函数(需要指定模块名)
import { mapState, mapGetters } from 'vuex'

export default {
  computed: {
    ...mapState('user', ['token']),
    ...mapGetters('user', ['isLogin'])
  },
  methods: {
    ...mapActions('user', ['login'])
  }
}
// 组件中访问模块 state
this.$store.state.user.token

// 命名空间后调用
this.$store.commit('user/SET_TOKEN', 'xxx')
this.$store.dispatch('user/login', data)

// 辅助函数(需要指定模块名)
import { mapState, mapGetters } from 'vuex'

export default {
  computed: {
    ...mapState('user', ['token']),
    ...mapGetters('user', ['isLogin'])
  },
  methods: {
    ...mapActions('user', ['login'])
  }
}

Mutation 类型管理 ​

js
// src/store/types.js
export const SET_USER = 'SET_USER'
export const SET_TOKEN = 'SET_TOKEN'
export const SET_LIST = 'SET_LIST'
// src/store/types.js
export const SET_USER = 'SET_USER'
export const SET_TOKEN = 'SET_TOKEN'
export const SET_LIST = 'SET_LIST'
js
import * as types from '../types'

export default {
  mutations: {
    [types.SET_USER](state, user) {
      state.user = user
    }
  }
}
import * as types from '../types'

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

Vuex 插件 ​

js
// 简单的持久化插件
const localStoragePlugin = store => {
  // 初始化时读取缓存
  const saved = localStorage.getItem('vuex-state')
  if (saved) {
    store.replaceState(JSON.parse(saved))
  }

  // 每次 mutation 后保存
  store.subscribe((mutation, state) => {
    localStorage.setItem('vuex-state', JSON.stringify(state))
  })
}

const store = new Vuex.Store({
  // ...
  plugins: [localStoragePlugin]
})
// 简单的持久化插件
const localStoragePlugin = store => {
  // 初始化时读取缓存
  const saved = localStorage.getItem('vuex-state')
  if (saved) {
    store.replaceState(JSON.parse(saved))
  }

  // 每次 mutation 后保存
  store.subscribe((mutation, state) => {
    localStorage.setItem('vuex-state', JSON.stringify(state))
  })
}

const store = new Vuex.Store({
  // ...
  plugins: [localStoragePlugin]
})
js
// 使用 vuex-persistedstate
import createPersistedState from 'vuex-persistedstate'

const store = new Vuex.Store({
  // ...
  plugins: [createPersistedState({
    storage: window.localStorage,
    paths: ['user.token', 'cart'] // 只持久化部分数据
  })]
})
// 使用 vuex-persistedstate
import createPersistedState from 'vuex-persistedstate'

const store = new Vuex.Store({
  // ...
  plugins: [createPersistedState({
    storage: window.localStorage,
    paths: ['user.token', 'cart'] // 只持久化部分数据
  })]
})

严格模式 ​

js
const store = new Vuex.Store({
  strict: process.env.NODE_ENV !== 'production' // 开发环境开启
  // 严格模式下,所有状态变更必须通过 mutation
})
const store = new Vuex.Store({
  strict: process.env.NODE_ENV !== 'production' // 开发环境开启
  // 严格模式下,所有状态变更必须通过 mutation
})

Vuex 4(Vue3) ​

js
import { createStore } from 'vuex'

export const store = createStore({
  state() {
    return { count: 0 }
  },
  mutations: {
    increment(state) {
      state.count++
    }
  }
})
import { createStore } from 'vuex'

export const store = createStore({
  state() {
    return { count: 0 }
  },
  mutations: {
    increment(state) {
      state.count++
    }
  }
})
js
import { createApp } from 'vue'
import { store } from './store'

const app = createApp(App)
app.use(store)
app.mount('#app')
import { createApp } from 'vue'
import { store } from './store'

const app = createApp(App)
app.use(store)
app.mount('#app')
vue
<!-- 组合式 API 中使用 -->
<script setup>
import { computed } from 'vue'
import { useStore } from 'vuex'

const store = useStore()

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

function increment() {
  store.commit('increment')
}
</script>
<!-- 组合式 API 中使用 -->
<script setup>
import { computed } from 'vue'
import { useStore } from 'vuex'

const store = useStore()

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

function increment() {
  store.commit('increment')
}
</script>

Pinia ​

Pinia 是 Vue 官方推荐的新一代状态管理工具,替代 Vuex。

安装与配置 ​

bash
npm install pinia
npm install pinia
js
// src/store/index.js
import { createPinia } from 'pinia'

export const pinia = createPinia()
// src/store/index.js
import { createPinia } from 'pinia'

export const pinia = createPinia()
js
// main.js
import { pinia } from './store'

app.use(pinia)
// main.js
import { pinia } from './store'

app.use(pinia)

定义 Store ​

选项式 API ​

js
// src/store/modules/user.js
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  state: () => ({
    name: '',
    token: ''
  }),
  getters: {
    isLogin: state => !!state.token,
    greeting: state => `Hello, ${state.name}!`
  },
  actions: {
    async login(username, password) {
      const res = await api.login({ username, password })
      this.token = res.token   // 直接修改 state(无需 mutation)
      this.name = username
    },
    logout() {
      this.token = ''
      this.name = ''
    }
  }
})
// src/store/modules/user.js
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  state: () => ({
    name: '',
    token: ''
  }),
  getters: {
    isLogin: state => !!state.token,
    greeting: state => `Hello, ${state.name}!`
  },
  actions: {
    async login(username, password) {
      const res = await api.login({ username, password })
      this.token = res.token   // 直接修改 state(无需 mutation)
      this.name = username
    },
    logout() {
      this.token = ''
      this.name = ''
    }
  }
})

组合式 API ​

js
// src/store/modules/counter.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)

  const doubleCount = computed(() => count.value * 2)

  function increment() {
    count.value++
  }

  return { count, doubleCount, increment }
})
// src/store/modules/counter.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)

  const doubleCount = computed(() => count.value * 2)

  function increment() {
    count.value++
  }

  return { count, doubleCount, increment }
})

使用 ​

vue
<script setup>
import { useUserStore } from '@/store/modules/user'

const userStore = useUserStore()

// 访问 state
console.log(userStore.name)
console.log(userStore.token)

// 访问 getters
console.log(userStore.isLogin)

// 调用 actions
userStore.login('admin', '123456')
userStore.logout()

// 直接修改 state(Pinia 支持直接修改)
userStore.name = '新名字'
userStore.$patch({ name: '新名字', token: 'xxx' })
</script>
<script setup>
import { useUserStore } from '@/store/modules/user'

const userStore = useUserStore()

// 访问 state
console.log(userStore.name)
console.log(userStore.token)

// 访问 getters
console.log(userStore.isLogin)

// 调用 actions
userStore.login('admin', '123456')
userStore.logout()

// 直接修改 state(Pinia 支持直接修改)
userStore.name = '新名字'
userStore.$patch({ name: '新名字', token: 'xxx' })
</script>

Vue2 中使用 Pinia ​

js
import { mapState } from 'pinia'
import { useUserStore } from '@/store/modules/user'

export default {
  computed: {
    ...mapState(useUserStore, ['user', 'token']),
    ...mapState(useUserStore, { userName: 'name' })
  }
}
import { mapState } from 'pinia'
import { useUserStore } from '@/store/modules/user'

export default {
  computed: {
    ...mapState(useUserStore, ['user', 'token']),
    ...mapState(useUserStore, { userName: 'name' })
  }
}

Pinia vs Vuex ​

特性VuexPinia
写法必须通过 mutation 修改 state直接修改 state
TypeScript支持较弱原生 TS 支持
模块嵌套支持 modules 嵌套支持,但去掉了嵌套
体积~10KB~1KB
DevTools支持支持
代码分割不支持支持(Store 自动分割)