Vuex 是什么
Vuex 是一个专为 Vue.js 应用程序开发的 状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态。
Vuex 的核心就是 store(仓库),store 就是一个容器,它用来保存项目中的状态(数据)。
Vuex 的状态存储是响应式的。当我们获取到了 store 中的状态,若 store 中的状态发生了变化,那么相应的组件也会被更新。
如果我们希望修改 store 中的状态,只能通过提交 mutation 来改变,而不能直接对状态进行修改。
this.$store.commit('increment') // 合法
this.$store.state.counter++ // 不合法
Vuex 的基本使用
计数案例
把数据放在组件中管理
<template>
<div>
<div>当前计数:{{ counter }}</div>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
</div>
</template>
export default {
data() {
return {
counter: 0
}
},
methods: {
increment() {
this.counter++
},
decrement() {
this.counter--
}
}
}
通过 vuex 来管理数据
如果我们通过 vuex 中来管理数据的话,步骤如下:
-
首先创建一个
store实例,这个store实例提供一个state对象和一些mutation; -
然后将
store实例挂载到根组件上,这样在其他组件中我们就可以通过this.$store获取到这个store实例。 -
在
options api中通过this.$store.state获取到状态对象,并通过this.$store.commit方法触发状态变更。 -
在
setup函数中,不能直接通过this.$store获取到store实例,需要通过useStore方法来获取。
代码如下:
import { createStore } from 'vuex'
// 1. 创建仓库,提供一个 state 对象和一些 mutation
// state 对象用来管理状态
// mutation 用来修改 state 中的状态,通过提交方法的方式
const store = createStore({
state() {
return {
counter: 0
}
},
mutations: {
increment(state) {
state.counter++
},
decrement(state) {
state.counter--
}
}
})
const app = createApp(App)
// 2. 把仓库挂载到根组件上
app.use(store)
在组件中:
<template>
<div>
<!-- 在模板中可以通过 $store.state 来获取到 state 对象 -->
<div>当前计数:{{ $store.state.counter }}</div>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
</div>
</template>
// Vue2
export default {
methods: {
increment() {
this.$store.commit('increment')
},
decrement() {
this.$store.commit('decrement')
}
}
}
// Vue3
import { useStore } from 'vuex'
export default {
setup() {
const $store = useStore()
return {
$store
}
}
}
State
单一状态树/单一数据源
所有的状态都保存在一个 store 中,也就是说一个项目中只有一个 store 实例。
在组件中获取 Vuex 状态
在实际开发中,如果需要多次获取,一般是跟计算属性结合使用。(这里涉及到 Vue3 中计算属性的使用方法,具体见这篇文章)
计算属性获取
如果每次都通过 $store.state.状态名 来获取状态会显得比较繁琐,所以我们一般会使用 计算属性。
// Vue2
export default {
computed: {
username() {
return this.$store.state.username
},
password() {
return this.$store.state.password
}
}
}
// Vue3
import { computed } from 'vue'
import { useStore } from 'vuex'
export default {
setup() {
// setup 函数中通过 useStore 方法获取 store 实例
const store = useStore()
const username = computed(() => store.state.username)
const password = computed(() => store.state.password)
return {
username,
password
}
}
}
这样在模板中我们直接使用计算属性就很方便
<template>
<div>
<div>{{ username }}</div>
<div>{{ password }}</div>
</div>
</template>
mapState 辅助函数
当一个组件需要获取多个状态值的时候,如果都手动一个个写成计算属性就会显得有点冗余。这时我们可以使用 mapState 辅助函数帮助我们生成计算属性。
mapState 函数返回一个对象。
mapState 函数根据传递过来的参数,将 返回对应状态的函数 返回。即:
mapState(['counter', 'username', 'password'])
// 相当于
return {
counter: function() {
return this.$store.state.counter
},
username: function() {
return this.$store.state.username
},
password: function() {
return this.$store.state.password
}
}
这样我们就可以使用 mapState 函数配合计算属性:
import { mapState } from 'vuex'
export default {
computed: {
...mapState(['counter', 'username', 'password'])
}
}
在模板中就可以直接使用了:
<template>
<div>
<div>{{ counter }}</div>
<div>{{ username }}</div>
<div>{{ password }}</div>
</div>
</template>
由于 mapState 函数返回的是一个对象,所以如果没有其他计算属性,则可以简写成下面这种形式:
computed: mapState(['counter', 'username', 'password'])
mapState 函数也可以配合 methods 来使用:
import { mapState } from 'vuex'
export default {
methods: {
...mapState(['counter', 'username', 'password'])
}
}
但是我们一般不推荐这么用。
手动实现一个 mapState 函数
function mapState(state) {
const obj = {}
state.forEach((i) => {
obj[i] = () => {
return this.$store.state[i]
}
})
return obj
}
在 setup 函数中使用 mapState 辅助函数
由于在 setup 函数中使用 computed 方法必须传入一个函数作为参数,所以我们不能像在 Vue2 中一样,直接把 mapState 函数的返回值赋值给 computed。
import { computed } from 'vue'
import { useStore } from 'vuex'
export default {
setup() {
const store = useStore()
const username = computed(() => store.state.username)
const password = computed(() => store.state.password)
return {
username,
password
}
}
}
所以我们只能通过另外的一种方式来使用 mapState 函数,代码如下:
import { computed } from 'vue'
import { useStore, mapState } from 'vuex'
export default {
setup() {
const store = useStore()
// 我们的目的是把下面这种手动来写计算属性的方式变成自动生成对应的计算属性
// const counter = computed(() => store.state.counter)
// const username = computed(() => store.state.username)
// const password = computed(() => store.state.password)
const storeStateFn = mapState(['counter', 'username', 'password'])
const storeState = {}
Object.keys(storeStateFn).forEach((key) => {
const fn = storeStateFn[key].bind({ $store: store })
storeState[key] = computed(fn)
})
return {
...storeState
}
}
}
我们可以将上面的代码封装成一个 hook,在需要的地方直接调用:
import { computed } from 'vue'
import { useStore, mapState } from 'vuex'
export function useState(mapper) {
const store = useStore()
const storeStateFn = mapState(mapper)
const storeState = {}
Object.keys(storeStateFn).forEach((key) => {
const fn = storeStateFn[key].bind({ $store: store })
storeState[key] = computed(fn)
})
return storeState
}
在组件中调用:
import { useState } from '../hooks/useState'
export default {
setup() {
const storeState = useState(['counter', 'username', 'password'])
return {
...storeState
}
}
}
Getters
state 中的属性需要进一步处理之后再使用,这个时候我们就可以使用 getters
getters 的基本使用
const store = createStore({
state() {
return {
counter: 10
}
},
getters: {
showCounter(state) {
return `当前计数:${state.counter}`
}
}
})
getters 类似于计算属性,可以在模板中直接使用:
<div>{{ $store.getters.showCounter }}</div>
getter 中可以调用其他的 getter
const store = createStore({
state() {
return {
username: 'kobe',
password: 123456
}
},
getters: {
showMessage(state, getters) {
return `用户名:${state.username} ${getters.showPassword}`
},
showPassword(state) {
return `密码:${state.password}`
}
}
})
<div>{{ $store.getters.showMessage }}</div>
getter 的参数
getter 一共有四个参数:state, getters, rootState, rootGetters,后面两个参数只有在模块化的时候才会用上。
getter 可以返回一个函数
在使用时可以直接调用这个函数。
const store = createStore({
state() {
return {
counter: 10
}
},
getters: {
showCounterAddN(state) {
return function (n) {
return `当前计数:${state.counter + n}`
}
}
}
})
因为 getters 不能传参,所以可以通过这种方式来传参。
<div>{{ $store.getters.showCounterAddN(12) }}</div>
mapGetters 辅助函数
使用方法同 mapState 函数一样,我们可以这样写:
import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters(['showCounter', 'showName', 'showPassword'])
}
}
<template>
<div>
<div>{{ showCounter }}</div>
<div>{{ showName }}</div>
<div>{{ showPassword }}</div>
</div>
</template>
在 setup 函数中使用 mapState 函数
同样在 setup 中使用时我们也可以封装一个 hook
import { computed } from 'vue'
import { useStore, mapGetters } from 'vuex'
export function useGetters(mapper) {
const store = useStore()
const storeStateFn = mapGetters(mapper)
const storeState = {}
Object.keys(storeStateFn).forEach((key) => {
const fn = storeStateFn[key].bind({ $store: store })
storeState[key] = computed(fn)
})
return storeState
}
封装 useMapper
由于 useState 跟 useGetters 中的代码大部分是重复的,所以我们封装了 useMapper
import { computed } from 'vue'
import { useStore } from 'vuex'
export function useMapper(mapper, mapFn) {
const store = useStore()
const storeStateFn = mapFn(mapper)
const storeState = {}
Object.keys(storeStateFn).forEach((key) => {
const fn = storeStateFn[key].bind({ $store: store })
storeState[key] = computed(fn)
})
return storeState
}
在 useGetters 中
import { mapGetters } from 'vuex'
import { useMapper } from './useMapper'
export function useGetters(mapper) {
return useMapper(mapper, mapGetters)
}
Mutations
基本使用
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation
const store = createStore({
state() {
return {
counter: 10
}
},
mutations: {
incrementN(state, payload) {
console.log(payload) // 数据
state.counter += payload.n
},
decrementN(state, payload) {
state.counter -= payload.n
}
}
})
我们在提交 mutation 的时候,可以携带数据过去。
export default {
methods: {
addN(n) {
// 第二个参数用来传递数据
this.$store.commit('incrementN', { n, name: 'zs', age: 18 })
// 也可以通过这种方式来提交 mutation
this.$store.commit({ type: 'incrementN', n, name: 'zs', age: 18 })
}
}
}
<div>{{ $store.state.counter }}</div>
<button @click="addN(10)">+10</button>
<button @click="addN(20)">+20</button>
mapMutations 辅助函数
mapMutations 函数返回一个对象。
mapMutations 函数根据传递过来的参数,将 返回对应提交的函数 返回。即:
mapMutations(['incrementN', 'decrementN'])
// 相当于
return {
incrementN: function(payload) {
return this.$store.commit('incrementN', payload)
},
decrementN: function(payload) {
return this.$store.commit('decrementN', payload)
}
}
配合 methods 使用:
methods: {
...mapMutations(['incrementN'])
}
在模板中使用:
<div>
<div>{{ $store.state.counter }}</div>
<button @click="incrementN({ n: 10, name: 'zs', age: 18 })">+10</button>
<button @click="incrementN({ n: 20, name: 'zs', age: 18 })">+20</button>
</div>
在 setup 函数中使用 mapMutations 辅助函数
直接使用,然后导出就可以了:
import { mapMutations } from 'vuex'
export default {
setup() {
const storeMutations = mapMutations(['incrementN', 'decrementN'])
return {
...storeMutations
}
}
}
<div>
<div>{{ $store.state.counter }}</div>
<button @click="incrementN({ n: 10, name: 'zs', age: 18 })">+10</button>
<button @click="incrementN({ n: 20, name: 'zs', age: 18 })">+20</button>
</div>
Actions
actions 类似于 mutations,区别在于, mutations 只允许进行同步的操作,而 actions 可以进行异步的操作。
action 必须通过提交 mutation 来修改 state,不能直接修改。
const store = createStore({
state() {
return {
data: {}
}
},
mutations: {
getData(state, payload) {
state.data = payload
}
},
actions: {
getDataAction(context, payload) { // context 参数是当前上下文,相当于 store 实例
axios.get('http://123.207.32.32:8000/home/multidata').then((res) => {
// 这里只能通过提交 mutation 来修改 state 中的状态,而不能直接进行修改
context.commit('getData', res.data.data)
})
}
}
})
// 分发一个 action,也可以传参
this.$store.dispatch('getDataAction', { name: 'zs', age: 18 })
action 的 context 参数是当前上下文,相当于 store 实例,我们可以从其中获取到 commit 方法来提交一个 mutation,或者通过 context.state 和 context.getters 来 获取 state 和 getters
mapActions 辅助函数
mapActions 函数的使用类似于 mapMutations
在 setup 函数中使用 mapActions 辅助函数
类似于 mapMutations