1. 介绍
Vuex是Vue官方推出的一个状态管理工具,其能生成一个单独的全局状态实例,其有以下特点:
- 能够在vuex中集中管理共享的数据,利于开发和后期的维护
- 能够在Vue的各个组件中实现访问和共享,能够有效解决以下兄弟组件、祖孙组件等跨代组件的通信困难问题。
- 存储在 vuex 中的数据都是响应式的,能够实时保持数据与页面的同步。
2. 安装
//CDN
https://unpkg.com/[email protected]
//NPM
npm i vuex@ 3.6.2 -s
注意事项:版本号在3.6.2以下的vuex适用于vue2,若在vue2的工程项目中直接npm i vuex -s
会报如下的错误
解决办法是指定版本号,目前vue2使用的版本目前最高支持到3.6.2,具体可在更新记录中进行查阅。
3.核心概念
3.1 State
State相对于vuex来说就好比vue组件中的data,所以声明state必须是纯粹的对象 (含有零个或多个的key/value
对),在建立后,State数据会被转换为响应式的数据。
3.1.1 State
的访问有以下的三种形式
- 在vue组件中(
tempalte
、data
、methods
、声明周期函数等)中访问 - 在vuex中访问
- 在其他js文件中访问
首先定义一个store,里面只包含State,然后我们将其注入到全局,下面一次进行以上三种形式的测试
//store.js
import Vue from "vue";
import Vuex from "vuex"
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
userName: "Vuex的学习之旅"
}
})
export default store
//main.js
import Vue from 'vue'
import App from './App.vue'
import store from "./store"
Vue.config.productionTip = false
new Vue({
render: h => h(App),
store
}).$mount('#app')
//App.vue
<template>
<div id="app">
<test-component></test-component>
</div>
</template>
<script>
import TestComponent from "./components/test-component.vue"
export default {
name: "app",
components: { TestComponent }
}
</script>
1.在vue组件中访问
<template>
直接在模板中使用$store.state...
进行store中state数据的访问<script>
通过this.$store.state...
进行store中state数据的访问//test-component <template> <div id="app"> <div>姓名:{{ $store.state.userName }}</div> </div> </template> <script> export default { name: 'test', data() { return { username: this.$store.state.userName } }, created() { console.log(this.$store.state.userName); console.log(this.username); } } </script>
注:由于不能修改store数据,所以一般不建议在data中访问store的数据,因为它没有缓存效果,一般在计算属性中获取state数据。
//test-component <template> <div id="app"> <div>姓名:{{ compute_username }}</div> <div>{{ compute_age }}</div> <div>{{ compute_sex }}</div> </div> </template> <script> export default { name: 'test', data() { return { description: "性别是" } }, computed: { compute_username() { return this.$store.state.userName }, compute_age() { return "年龄" + this.$store.state.age }, compute_sex() { return this.description + this.$store.state.sex } } } </script>
在vuex中访问
如果vuex中访问state的数据,一般是在mutation
、getters
中可以通过this.state...
进行获取//store.js import Vue from "vue"; import Vuex from "vuex" Vue.use(Vuex) const store = new Vuex.Store({ state: { userName: "Vuex的学习之旅" }, mutations: { set_userName(state, data) { this.state.userName = data //等同于 state.userName = data } } }) export default store
//test-component <template> <div id="app"> <div>姓名:{{ $store.state.userName }}</div> </div> </template> <script> export default { name: 'test', data() { return { username: this.$store.state.userName } }, created() { this.$store.commit("set_userName", "修改了vuex的名字") } } </script>
在其他js文件中访问
在其它js文件中访问就需要先行引入store.js,然后对这个store.js文件导出的对象进行取值等操作。// other.js import store from "./store" console.log(store.state.userName);
3.1.2 mapState
语法糖
mapState
语法糖的主要作用是将store中的state映射到当前vue实例的computed中
在没有mapState
时,我们获取state一般在computed中进行获取,对于获取多个state数据,那么我们就要写多个computed方法,如下
//store.js
import Vue from "vue";
import Vuex from "vuex"
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
userName: "Vuex的学习之旅",
age: 18,
sex: "男"
}
})
export default store
//test-component
<template>
<div id="app">
<div>姓名:{{ compute_username }}</div>
<div>{{ compute_age }}</div>
<div>{{ compute_sex }}</div>
</div>
</template>
<script>
export default {
name: 'test',
data() {
return {
description: "性别是"
}
},
computed: {
compute_username() {
return this.$store.state.userName
},
compute_age() {
return "年龄" + this.$store.state.age
},
compute_sex() {
return this.description + this.$store.state.sex
}
}
}
</script>
针对于写多个computed的繁琐和冗余,使用mapState
可以帮助我们生成计算属性
。
3.1.2.1 mapState
对象写法
//test-component
<template>
<div id="app">
<div>姓名:{{ compute_username }}</div>
<div>{{ compute_age }}</div>
<div>{{ compute_sex }}</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'test',
data() {
return {
description: "性别是"
}
},
computed: mapState({
compute_username(state) {
return state.userName
//等同于return this.$store.state.userName
},
//ES6箭头函数
//compute_username:state => state.userName
//compute_username:state => this.$store.state.userName
// 传字符串参数 'age' 等同于 `state => state.age`
compute_age: 'age',
//需要配合组件实例中的其它数据,使用普通函数的形式,才能保证this的指向
compute_sex(state) {
return this.description + state.sex
}
})
}
</script>
3.1.2.1 mapState
数组写法
当映射的计算属性的名称与 state 的数据名称相同时,我们也可以给 mapState 传一个字符串数组。
<template>
<div id="app">
<div>姓名:{{ userName }}</div>
<div>{{ age }}</div>
<div>{{ sex }}</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'test',
data() {
return {
description: "性别是"
}
},
computed: {
userName() {
return this.$store.state.userName
},
age() {
return this.$store.state.age
},
sex() {
return this.$store.state.sex
}
}
}
</script>
<template>
<div id="app">
<div>姓名:{{ userName }}</div>
<div>{{ age }}</div>
<div>{{ sex }}</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'test',
data() {
return {
description: "性别是"
}
},
computed: mapState(["userName", "age", "sex"])
}
</script>
3.1.2.2 mapState
使用剩余展开运算符
mapState
返回的是一个对象,如果computed只包括mapState
,那么直接写
computed: mapState(["userName", "age", "sex"])
// 等同于
computed: {
...mapState(["userName", "age", "sex"])
}
如果你的computed还有其它store之外的属性,那么你需要使用剩余运算符,把mapState返回的对象和其它计算属性对象合并
<template>
<div id="app">
<div>姓名:{{ userName }}</div>
<div>{{ age }}</div>
<div>{{ sex }}</div>
<div>{{ getSexEnglishType }}</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'test',
data() {
return {
description: "性别是"
}
},
computed: {
...mapState(["userName", "age", "sex"]),
getSexEnglishType() {
return this.sex == "男" ? "boy" : "girl"
}
}
}
</script>
4. getters
顾名思义,getters就好比是store的计算属性computed,只有当store中的依赖发生了改变,才会重新触发getters。
import Vue from "vue";
import Vuex from "vuex"
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
carList: [{
type: "公交车",
count: 1
}, {
type: "汽车",
count: 3
}, {
type: "出租车",
count: 5
}, {
type: "卡车",
count: 7
}]
},
getters: {
// getCars(state){
// return state.carList.filter(car=>car.count>4)
// }
getCars: state => state.carList.filter(car => car.count > 4)
}
})
export default store
4.1 getters的访问
同state获取的方式一样,getters的获取分为以下三种
- 在vue组件中(
tempalte
、data
、methods
、声明周期函数等)中访问 - 在vuex中访问
- 在其他js文件中访问
其三类的实现逻辑与state相近,这里只介绍在vue组件中访问
4.1.1在组件中通过属性访问
通过属性访问就是直接获取store的getters,再点出具体的获取属性即可
// test-component.vue
<script>
import { mapGetters } from 'vuex'
export default {
name: 'test',
data() {
return {
description: "性别是"
}
},
computed: {
getCars() {
return this.$store.getters.getCars
}
}
}
</script>
getters第一个参数是state,第二个参数是getters,那么我们就可以在一个getters方法中使用第二个参数嵌套使用其他的getters
//store.js
import Vue from "vue";
import Vuex from "vuex"
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
carList: [{
type: "公交车",
count: 1
}, {
type: "汽车",
count: 3
}, {
type: "出租车",
count: 5
}, {
type: "卡车",
count: 7
}]
},
getters: {
// getCars(state){
// return state.carList.filter(car=>car.count>4)
// }
getCars: (state,getters) => getters.formateCars,
formateCars(state,getters){
return state.carList.filter(car => car.count > 4)
}
}
})
export default store
// test-component.vue
<template>
<div id="app">
<div>车辆:{{ getCars }}</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'test',
computed: {
getCars(state,getters) {
return this.$store.getters.getCars
},
}
}
</script>
4.1.2 通过方法访问
getter像computed方法一样,可以利用返回的匿名函数实现参数的传递,但是这样会导致缓存的作用消失,当然computed如果也是使用返回的匿名函数的方式的调用,那么computed的缓存效果也会消失。
import Vue from "vue";
import Vuex from "vuex"
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
carList: [{
type: "公交车",
count: 1
}, {
type: "汽车",
count: 3
}, {
type: "出租车",
count: 5
}, {
type: "卡车",
count: 7
}]
},
getters: {
getCars: (state,getters) => count => state.carList.filter(car=>car.count == count)
}
})
export default store
// test-component.vue
<template>
<div id="app">
<div>车辆:{{ getCars(5) }}</div>
</div>
</template>
<script>
export default {
name: 'test',
computed: {
getCars(state,getters) {
return this.$store.getters.getCars
},
}
}
</script>
4.2 mapGetters
语法糖
同mapState
的语法糖一样,mapGetters
语法糖也分为对象形式和数组形式,此处就不再赘述。
5. Mutation
mutation是唯一能够更改Vuex中store的state状态的唯一途径,mutation非常类似于一个回调事件,其键名为字符串的事件类型,其值是一个回调函数,回调函数就是我们修改state的地方,回调函数的第一个参数是state,第二个是可选参数,他是我们调用的时候所传入的参数
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
add (state) {
// 变更状态
state.count++
}
}
})
mutation是一个回调事件,所以我们不能直接进行调用,而正确的调用方法是触发这个事件的名字
store.commit('add')
5.1 commit参数
store在commit可以传入参数,其参数分为以下两种形式
- 载荷形式
- 对象形式
载荷形式
const store = new Vuex.Store({ state: { count: 1 }, mutations: { add (state,data) { // 变更状态 state.count += data.count } } }) store.commit('increment', { count: 10 })
const store = new Vuex.Store({ state: { count: 1 }, mutations: { add (state,count) { // 变更状态 state.count += data.count } } }) store.commit('increment',10)
对象形式
对象形式是指直接使用包含type
属性的对象,当然以这种形式commit所对应的函数的第二个参数肯定是一个对象的类型。const store = new Vuex.Store({ state: { count: 1 }, mutations: { add (state,data) { // 变更状态 state.count += data.count } } }) store.commit({ type: 'increment', count: 10 })
5.2 mutation修改store的正确方式
同vue的data一样,如果是对象形式的data,如果我们在对象类型的state上需要增添我们的新属性的时候可以采用如下方案
Vue.set(obj, 'newProp', 123)
利用展开运算符合并一个新对象
state.obj = { ...state.obj, newProp: 123 }
- 直接让state中的数据等一一个新对象,再使用
vue.forceupdatevue.forceupdate
(偏方,会强制刷新页面,损失页面性能)
5.3 使用常量type来作为mutation属性的键名
使用常量替代 mutation 事件类型在各种,同时把这些常量放在单独的文件中可以让你的代码合作者对整个 app 包含的 mutation 一目了然,
具体而言,store.commit('add')
,可以发现,这里 commit 提交的方法 add,是以字符串的形式代入的。如果项目小,一个人开发的话倒还好,但是项目大了,编写代码的人多了,那就麻烦了,因为需要 commit 的方法一多,就会显得特别混乱,而且以字符串形式代入的话,一旦出了错,很难排查。
// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'
// store.js
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'
const store = new Vuex.Store({
state: { ... },
mutations: {
// 我们可以使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名
[SOME_MUTATION] (state) {
// mutate state
}
}
})
5.4 Mutation 必须是同步函数
为什么Mutation 必须是同步函数呢,这本质是因为它是一个回调函数,回调函数在异步的时候无法得知哪个先完成哪个后完成,比如
foreach(async () => {
await ...
})
因此,这样会导致一个结果,就是state中的数据不知道什么时候发生的改变的(不过也确实是改变了的),所以state不能使用异步函数,这样会丢失了其可追踪性。
5.5 Mutation的提交
mutation的提交有两种方式
- 组件中直接通过
store.commit(type)
提交 - 通过
mapMutations
辅助函数提交
5.5.1 组件直接提交
import Vue from "vue";
import Vuex from "vuex"
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
count: 1
},
mutations:{
add(state){
state.count += 1
}
}
})
export default store
<template>
<div id="app">
<div>车辆:{{ $store.state.count }}</div>
<button @click="$store.commit('add')">增加</button>
</div>
</template>
<script>
export default {
name: 'test'
}
</script>
或
<template>
<div id="app">
<div>车辆:{{ $store.state.count }}</div>
<button @click="add">增加</button>
</div>
</template>
<script>
export default {
name: 'test',
methods:{
add(){
this.$store.commit('add')
}
}
}
</script>
5.5.2 mapMutations语法糖
mapMutations可以将store中的commit(不是mutation)方法映射为当前vue组件中的方法,其有两种方式
- 数组形式的映射
- 对象形式的映射
5.5.2.1 mapMutations数组形式的映射
数组形式的映射很简单,就把commit的字符串参数映射为当前组件的方法,它们的名称是一致的,那么就可以通过数组进行映射。
import { mapMutations } from 'vuex'
export default {
// ...
methods: {
...mapMutations([
'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`
// `mapMutations` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
])
}
}
5.5.2.1 mapMutations对象形式的映射
对象形式的映射可以把commit的mutation名称映射为当前组件的其它方法名称
import { mapMutations } from 'vuex'
export default {
// ...
methods: {
...mapMutations([
'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`
// `mapMutations` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
]),
...mapMutations({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
})
}
}