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 pinianpm install piniajs
// 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
| 特性 | Vuex | Pinia |
|---|---|---|
| 写法 | 必须通过 mutation 修改 state | 直接修改 state |
| TypeScript | 支持较弱 | 原生 TS 支持 |
| 模块嵌套 | 支持 modules 嵌套 | 支持,但去掉了嵌套 |
| 体积 | ~10KB | ~1KB |
| DevTools | 支持 | 支持 |
| 代码分割 | 不支持 | 支持(Store 自动分割) |
Cao Note