共计 8827 个字符,预计需要花费 23 分钟才能阅读完成。
1. 简介
Vuex 是一个 Vue 的状态管理工具,状态就是数据。它的使用场景是多个组件共同维护一份数据(如购物车),或者多个组件使用同一个状态(个人信息)。
其实就是集中式的管理数据,如下图所示:
官方文档:https://vuex.vuejs.org/zh/
2. 基本使用
vue2 中使用 vuex
分为以下几个步骤:
- 安装
vuex
- 引入
vuex
- 注册
vuex
插件 - 创建
Vuex Store
实例 - 将
Vuex Store
注入Vue
实例
npm i vuex@3
接下来要创建 Vuex Store
,一般会在 src
目录下新建一个 store/index.js
文件。
// 导入 vuex
import Vue from 'vue'
import Vuex from 'vuex'
// vuex 也是 vue 的插件, 需要 use 一下, 进行插件的安装初始化
Vue.use(Vuex)
// 创建 Vuex Store 实例
export default new Vuex.Store({
state: {
count: 1
}
})
在 main.js
中引入 Vuex Store
,并注入。
import store from './router'
new Vue({
el: '#app',
components: { App },
template: '<App/>',
store
})
在组件中这样使用即可:
<template>
<div id="app">
<p>Count: {{ $store.state.count }}</p>
</div>
</template>
<script>
export default {
name: 'HelloWorld'
}
</script>
3. 核心概念
3.1 State
Vuex 使用单一状态树,这意味着每个应用将仅仅包含一个 store 实例。存储在 Vuex 中的数据和 Vue 实例中的 data
遵循相同的规则,例如状态对象必须是纯粹 (plain) 的。
Vue Store
定义如下:
// 导入 vuex
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 1
}
})
组件中可以通过获取 Vuex Store
的实例 $store
获取数据。
<template>
<div id="app">
<p>Count: {{ $store.state.count }}</p>
</div>
</template>
<script>
export default {
name: 'HelloWorld'
}
</script>
这里需要注意的是,不要将 state
数据变为 data
数据,这样会导致和其他组件状态不统一。更好的做法是,将其变成一个计算属性。
<template>
<div id="app">
<p>Count: {{ count }}</p>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
computed: {
count () {
return this.$store.state.count
}
}
}
</script>
计算属性虽好,但是如果要获取很多个状态时,代码显得非常冗余。这个时候可以使用 mapState 辅助函数获取。
mapState 使用数组的方式获取多个状态,方式如下:
<template>
<div id="app">
<p>Count: {{ count }}</p>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'HelloWorld',
computed: mapState(['count', 'num']
// 相当于
// count(){return this.$store.state.count}
// num(){return this.$store.state.num}
}
</script>
但是当组件有其他计算属性时,通过数组就不太合理了,这时候需要 es6 中的展开运算符:
<template>
<div id="app">
<p>Count: {{ count }}</p>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'HelloWorld',
data() {
return {
value: 17
}
},
computed: {
doubleValue () {
return this.value * 2
},
...mapState(['count','num'])
}
}
</script>
3.2 getters
getters 相当于 vue 中的计算属性,通过 getters 进一步处理,得到想要的值,而且允许传参,第一个参数就是 state。
Vue Store
定义如下:
// 导入 vuex
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 1
},
getters: {
doubleCount(state) {
return state.count * 2
}
}
})
组件中使用:
<template>
<div id="app">
<p>Count: {{ count }}</p>
<p>Double Count: {{ doubleCount }}</p>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
computed: {
// 上一节中提到的
...mapState(['count']),
doubleCount() {
return this.$store.getters.doubleCount
}
}
}
</script>
还可以通过 mapGetters 辅助函数帮助我们生成计算属性:
<template>
<div id="app">
<p>Count: {{ count }}</p>
<p>Double Count: {{ doubleCount }}</p>
</div>
</template>
<script>
import { mapState, mapGetters } from 'vuex'
export default {
name: 'HelloWorld',
computed: {
...mapState(['count']),
...mapGetters(['doubleCount'])
},
}
</script>
3.3 mutation
mutation 类似于 vue 中的 methods,它是更改 Vuex 的 store 中的状态的唯一方法。并且,mutation 中的方法都必须是同步的。
每个 mutation 都有一个字符串的事件类型 (type)和一个回调函数 (handler)。这个回调函数就是实际进行状态更改的地方,并且它会接受 state 作为第一个参数。
需要注意的是,不能直接调用一个 mutation 处理函数,而需要以相应的 type 调用 store.commit 方法。
Vue Store
定义如下:
// 导入 vuex
import Vue from 'vue'
import Vuex from 'vuex'
// vuex 也是 vue 的插件, 需要 use 一下, 进行插件的安装初始化
Vue.use(Vuex)
// 创建 Vuex Store 实例
export default new Vuex.Store({
state: {
count: 1
},
getters: {
doubleCount(state) {
return state.count * 2
}
}
mutations: {
increment(state) {
state.count++
}
}
})
组件中使用:
<template>
<div id="app">
<p>Count: {{ count }}</p>
<p>Double Count: {{ doubleCount }}</p>
<button @click="increment">同步+1</button>
</div>
</template>
<script>
import { mapState, mapGetters } from 'vuex'
export default {
name: 'HelloWorld',
data() {
return {
value: 17
}
},
computed: {
...mapState(['count']),
...mapGetters(['doubleCount'])
},
methods: {
increment() {
this.$store.commit('increment')
}
}
}
</script>
同样可以使用 mapMutations
简化:
<template>
<div id="app">
<p>Count: {{ count }}</p>
<p>Double Count: {{ doubleCount }}</p>
<button @click="increment">同步+1</button>
<button @click="add">同步+1</button>
</div>
</template>
<script>
import { mapState, mapGetters, mapMutations } from 'vuex'
export default {
name: 'HelloWorld',
data() {
return {
value: 17
}
},
computed: {
...mapState(['count']),
...mapGetters(['doubleCount'])
},
methods: {
...mapMutations(['increment']),
// 将 this.add() 映射为 this.$store.commit('increment')
...mapMutations({
add: 'increment'
}),
}
}
</script>
可以向 store.commit
传入额外的参数:
// 定义
mutations: {
increment (state, n) {
state.count += n
}
}
// 使用
store.commit('increment', 10)
大多数情况下,参数应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读:
// 定义
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
// 使用
store.commit('increment', {
amount: 10
})
3.4 action
Action 类似于 mutation,不同在于:
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作。
action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit
提交一个 mutation,或者通过 context.state
和 context.getters
来获取 state 和 getters。然而,context 并不是 store 实例本身。
Vue Store
定义如下:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 1
},
getters: {
doubleCount(state) {
return state.count * 2
}
},
mutations: {
increment(state) {
state.count++
}
},
actions: {
incrementAsync(context) {
setTimeout(() => {
context.commit('increment')
}, 1000)
}
}
})
组件中使用:
<template>
<div id="app">
<p>Count: {{ count }}</p>
<p>Double Count: {{ doubleCount }}</p>
<button @click="increment">同步+1</button>
<button @click="add">同步+1</button>
<button @click="incrementAsync">异步+1</button>
<button @click="addAsync">异步+1</button>
</div>
</template>
<script>
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
export default {
name: 'HelloWorld',
computed: {
...mapState(['count']),
...mapGetters(['doubleCount'])
},
methods: {
...mapMutations(['increment']),
...mapMutations({
add: 'increment'
}),
...mapActions(['incrementAsync']),
...mapActions({
addAsync: 'incrementAsync'
})
}
}
</script>
actions 支持同样接受参数,还能支持对象形式分发:
// 以载荷形式分发
store.dispatch('incrementAsync', {
amount: 10
})
// 以对象形式分发
store.dispatch({
type: 'incrementAsync',
amount: 10
})
Action 通常是异步的,那么如何知道 action 什么时候结束呢?更重要的是,如何才能组合多个 action,以处理更加复杂的异步流程?
首先,需要明白 store.dispatch
可以处理被触发的 action 的处理函数返回的 Promise,并且 store.dispatch
仍旧返回 Promise:
state: {
count: 1
},
mutations: {
increment(state) {
state.count++
}
},
actions: {
incrementAsync({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('increment')
resolve()
}, 1000)
})
}
}
现在可以使用:
methods: {
incrementAsync() {
this.$store.dispatch('incrementAsync')
.then(() => {
console.log('incrementAsync 完成')
})
}
}
还可以:
state: {
count: 1
},
mutations: {
increment(state) {
state.count++
}
},
actions: {
incrementAsync({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('increment')
resolve()
}, 1000)
})
},
// 调用两次 increment
incrementAsync2({ dispatch, commit }) {
return dispatch('incrementAsync').then(() => {
commit('increment')
})
}
}
4. 模块化开发
4.1 简单使用
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块。
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const counter = {
state: {
count: 0
}
}
const user = {
state: {
name: 'zhangsan'
}
}
const modules = {
counter,
user
}
export default new Vuex.Store({
modules
})
在组件中使用:
<template>
<div id="app">
<p>Count: {{ $store.state.counter.count }}</p>
<p>Name: {{ $store.state.user.name }}</p>
</div>
</template>
4.2 模块抽取
其实就是将模块抽离到单独的文件中,然后通过 import
引入。
counter.js
const state = {
count: 1
}
const getters = {
doubleCount: state => state.count * 2
}
const mutations = {
increment(state) {
state.count++
}
}
const actions = {
incrementAsync(context) {
setTimeout(() => {
context.commit('increment')
}, 1000)
}
}
export default {
namespaced: true,
state,
getters,
mutations,
actions
}
user.js
const state = {
name: "zhangsan"
}
const getters = {
upperCaseName(state) {
return state.name.toUpperCase()
}
}
const mutations = {
changeName(state) {
state.name = 'lisi'
}
}
const actions = {
changeNameAsync({ commit }) {
setTimeout(() => {
commit("changeName")
}, 1000)
}
}
export default {
namespaced: true,
state,
getters,
mutations,
actions
}
当在一个模块中设置了 namespaced: true 时,该模块的所有 action、mutation 和 getter 都会被限定在该命名空间下。这意味着在组件中访问这些模块的内容时,需要指定命名空间
如果没有设置 namespaced: true,则所有模块的 action、mutation 和 getter 都会注册在全局命名空间下,可能导致命名冲突和混乱。
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
import counter from './modules/counter'
Vue.use(Vuex)
const modules = {
counter,
user
}
console.log(modules)
export default new Vuex.Store({
modules
})
组件中使用:
<template>
<div id="app">
<p>Count: {{ count }}</p>
<p>Double Count: {{ doubleCount }}</p>
<button @click="increment">同步+1</button>
<button @click="incrementAsync">异步+1</button>
<p>Name: {{ name }}</p>
<p>UpperCase Name: {{ upperCaseName }}</p>
<button @click="changeName">修改名称</button>
<button @click="changeNameAsync">异步修改名称</button>
</div>
</template>
<script>
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
export default {
name: 'HelloWorld',
data() {
return {
value: 17
}
},
created() {
console.log(this.$store)
},
computed: {
...mapState({
count: state => state.counter.count,
name: state => state.user.name
}),
// 方式一
// ...mapGetters('counter', [ 'doubleCount' ]),
// ...mapGetters('user', [ 'upperCaseName' ]),
// 方式二
...mapGetters({
doubleCount: 'counter/doubleCount',
upperCaseName: 'user/upperCaseName'
})
},
methods: {
// 方式一
// ...mapMutations('counter', ['increment']),
// ...mapMutations('user', ['changeName']),
//...mapActions('counter', ['incrementAsync']),
//...mapActions('user', ['changeNameAsync'])
// 方式二
...mapMutations({
increment: 'counter/increment',
changeName: 'user/changeName'
}),
...mapActions({
incrementAsync: 'counter/incrementAsync',
changeNameAsync: 'user/changeNameAsync'
})
}
}
</script>
提醒:本文发布于217天前,文中所关联的信息可能已发生改变,请知悉!