Vuex使用教程

预览图.png

Vuex 就是前端为了方便数据的操作而建立的一个” 前端数据库“,用来管理所有组件的数据。
state 就是数据库
getter 用来 获取 数据
mutation 用来定义一些方法对数据进行 修改/存储
actions 呢。你想呀,后端从前端拿到了数据,总要做个异步处理吧,处理完了再存到数据库中(.then( () => {异步处理完成后执行}))。其实这就是
action的过程。如果是个同步操作,那么可以直接丢到数据库,所以vuex也可以在
action 中直接存,就是直接mutation。
在我看来,Vuex 相当于某种意义上设置了读写权限的全局变量,将数据保存保存到该“全局变量”下,并通过一定的方法去读写数据。

为什么使用Vuex


如图所示,顶层父组件App有一个数据a:123,组件A想要获取这个数据,必须通过props一层层向下传App -> G -> A,B组件也想获取数据a,必须通过props一层层向下传App -> E -> D ->B

如果B组件有个按钮,点击后想要修改a的值,则必须通过自定义事件$emit()一层层向上通知父组件B -> D -> E -> App


如图所示,用了Vuex后,相当于建立了一个前端数据库,所有的组件都可以直接获取到Vuex里的数据,并且可以直接修改数据

使用场景

  • 当一个项目的组件的层级有好几层,而且有些组件需要获取同一个数据时(避免数据一层层向下传递)
  • 当不同的组件,它们有个事件是需要更改同一个数据时(避免自定义事件一层层向上传)
  • 全局共享的数据使用vuex,比如用户信息,因为用户的权限,头像,昵称等等常常会出现在各个不同的组件中,此外,不需要实时更新的数据,都需要保存起来,减少不必要的http请求

    Vuex的数据变化过程

    前端通过 action 进行异步处理数据后通过context.commit传递给mutation,然后通过 mutation 把数据放入数据库(state)中,哪个组件要用就通过
    getter 从数据库(state)中取。

  • 更改 Vuex 的 store 中的状态的唯一方法是提交(commit) mutation

  • 同步逻辑可以直接写到 mutation 里面,通过 this.$store.commit('xxx')进行提交
  • 异步逻辑都应该封装到 actions 里面,通过 this.$store.dispatch('xxx') 方法调用

导出与注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//store.js
//使用Vuex
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
//实例化
const store = new Vuex.Store({
state: {},
getters: {},
mutations: {},
actions: {}
})
export default store;

将store.js导入到入口文件,挂载到Vue实例的配置项里

1
2
3
4
5
6
7
8
9
10
//main.js
import Vue from 'vue'
import router from './router/index.js'
import store from './vuex/index.js'
new Vue({
el:'#app',
router,
store,
})

state 数据

1
2
3
4
state: {
lists: [],
count:0
}

在组件中获取state

1
<div>{{ count }}</div>

通过this.$store.state.xxx就可以获取state里的数据了

1
2
3
4
5
6
7
8
9
//组件
computed: {
count(){
return this.$store.state.count;
},
firstName(){
return this.$store.state.firstName;
}
},

利用辅助函数mapState获取数据
语法:...mapState(['数据名1','数据名2'])

1
2
3
computed: {
...mapState(['count','firstName','lastName']),
}

也可以直接在html中通过$store.state获取【不推荐这种写法】

1
2
3
<div id="app">
{{ $store.state.count }}
</div>

mutation 变更

mutation对state里的数据进行 修改/保存
mutation里的方法,第一个参数始终是state,后面可以传入额外的参数
文档:mutations

1
2
3
4
5
6
mutations: {
getLists(state,lists){
//变更状态
state.lists = lists;
},
}

在组件中提交mutation(变更)请求

如果mutations里的方法是同步操作,那么组件可以直接提交commit请求

1
2
3
4
5
6
7
//store.js
mutations: {
increment(state,payload){
state.count += payload.add;
}
}

1
2
3
4
5
6
7
//组件中
methods: {
btnClick(){
this.$store.commit('increment',{add:10});
}
}

你也可以直接在html中通过$store.commit提交变更请求

1
2
3
<div id="app">
<p @click="$store.commit('increment',{add:10})"> {{$store.state.count}}</p>
</div>

如果mutations里的方法需要异步执行,怎么办(比如setTimeout,1秒之后才执行state.count++)?通过actions!

actions 调用

文档:action
在actions中,通过context.commit()来调用mutations里的方法

1
2
3
4
5
6
7
8
9
actions: {
getLists(context){
axios.get('www.xxx.com').then(({data})=> {
/调用mutations里的init方法,并将data.lists作为参数传过去
context.commit('init',data.lists);
})
.catch(error => document.write(error));
},
}

实践中,我们会经常用到 ES2015 的 参数解构 来简化代码(特别是我们需要调用 commit 很多次的时候):

1
2
3
4
5
6
7
8
9
actions: {
getLists({commit, state}){ //对象的解构赋值
axios.get('www.xxx.com').then(({data})=> {
commit('init',data.lists);
})
},
}

在组件中提交action(调用)请求

在组件中,通过dispatch调用action,然后store.state.xxx获取数据
文档:组件通过dispatch调用vuex里的actions
组件通过this.$store.dispatch()调用store实例里面 actions里定义的方法,然后在计算属性computed里通过return this.$store.state获得state里的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//组件Address.vue
<script>
export default {
//将getter到的数据放在计算属性里
computed: {
lists(){
return this.$store.state.lists;
}
},
created(){
if(!lists.length){
//通过dispatch调用actions里的方法,返回想要的数据
this.$store.dispatch('getLists');
}
}
}
</script>

mapActions

mapActions 映射为 this.$store.dispatch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { mapActions } from 'vuex'
export default {
methods: {
...mapActions([
'addAddress', // 将 `this.addAddress()` 映射为 `this.$store.dispatch('addAddress')`
// `mapActions` 也支持载荷:
'removeAddress' // 将 `this.removeAddress(xxx)` 映射为 `this.$store.dispatch('removeAddress', xxx)`
]),
...mapActions({
yyy: 'getMyAddress' // 将 `this.yyy()` 映射为 `this.$store.dispatch('getMyAddress')`
})
}
}

mapState

mapState 映射为 this.$store.state.xxx
当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余。此时可以用使用 mapState 辅助函数,然后将数据放入组件的计算属性内

1
2
3
4
5
6
7
8
import { mapState } from 'vuex'
export default {
computed: mapState({
// 传字符串参数 'count'
xxx: 'count', //此时,this.xxx = store.state.count
})
}

如果映射的计算属性的名称与 state 里的名称相同时,我们也可以给 mapState 传一个字符串数组。

1
2
3
4
computed: mapState([
// 映射 this.count 为 store.state.count
'count'
])

如果该组件有自己的计算属性,怎么办?混入计算属性

1
2
3
4
5
6
7
8
computed: {
xxx() { /* ... */ },
// 使用对象展开运算符将store.state里的数据混入computed
...mapState({
count: 'count',
})
}

像上面这种key和value相同的情况,可以将对象写成数组的形式

1
2
3
computed: {
...mapState(['count']),
}

Getter

getter类似Vue中的计算属性

有时候我们需要从 store 中的 state 中派生出一些状态,例如需要将state中的firstName和lastName合并,这时需要定义一个计算属性fullName

1
2
3
4
5
6
//main.js
computed: {
fullName () {
return this.$store.state.firstName + this.$store.state.lastName;
}
}

如果有多个组件需要用到此属性,我们就得每次都手写一遍这个计算属性的代码,Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//store.js
const store = new Vuex.Store({
state: {
firstName: 'Harry',
lastName: 'Potter'
},
getters: {
fullName: state => {
return state.firstName + state.lastName;
},
//推荐写成下面这种写法
fullName(state){
return state.firstName + state.lastName;
}
},
})

然后,所有的组件都可以通过this.$store.getters.fullName获取这个属性文档:Getter

1
2
3
4
5
6
7
//在子组件中
computed: {
fullName(){
return this.$store.getters.fullName;
}
}

等同于

1
2
3
4
5
//在子组件中
computed:{
...mapGetters(['fullName']);
}

!注意,store.js自身无法获取getters里面的数据;在Vue中,可以直接获取计算属性,但是在Vuex中,无法获取getters里的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//store.js
const store = new Vuex.Store({
state: {
a:1,
},
getters: {
aPlus: state => {
return state.a + 1;
}
},
mutations: {
xxx(state){
this.aPlus + 1; //警告!这是错误的,因为store.js只能操作state里的数据,无法操作getters里的数据
},
}
})

其实,从mutation的第一个参数可以看出,它是通过第一个参数 state 来间接修改state里的数据;而mutation内部并没有提供getters这个参数

mapGetters

文档:mapGetters
mapGetters 映射为 this.$store.getters.xxx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { mapGetters } from 'vuex'
export default {
computed: {
// 使用对象展开运算符将 getter 混入 computed 对象中
...mapGetters([
'a',
'others',
// ...
]),
...mapGetters({
b:'xxx', //如果key和value不同名,数组要改写成对象的形式
})
}
}

关于辅助函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import store form './store.js' //千万别忘了把store注入到Vue实例内!!!!
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
new Vue({
el: "#app",
store,
computed: {
//state和getters放在计算属性内
...mapState(['lists']),
...mapGetters(['aPlus']),
},
methods: {
//mutations和actions放在methods内
...mapMutations(['add','remove']),//同步操作,可以直接通过mutation修改state里的数据,不需要借助action的commit
...mapActions(['asyncFn']),//异步操作
}
})

辅助函数传递payload

载荷要在html中传递过去,而不是在methods里!!!!

1
<button @click="add(5)">+5</button>

1
2
3
4
5
//组件中
methods: {
...mapMutations(["add"])
}
1
2
3
4
5
//store.js
mutations: {
add(state,n) {
state.count += n;
},

参考地址:https://segmentfault.com/q/1010000011006052

另一种方式传递payload

单独在methods里定义一个方法来调用

1
2
3
4
5
6
methods:{
...mapActions(['add']),
handleClick(){
this.add({amount:100})
}
}

1
<button @click="handleClick">登录</button>

参考地址:https://segmentfault.com/q/1010000009563211

module

如果将所有页面的数据全部放在一个store里,会显得十分臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、getter、mutation、action

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// src/store/index.js
//新建文件夹modules,用来放模块
//使用Vuex
import Vue from 'vue';
import Vuex from 'vuex';
import user from './modules/user.js';
import blog from './modules/blog.js';
Vue.use(Vuex);
const store = new Vuex.Store({
modules: {
user,
blog
}
});
export default store;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// user.js 和 blog.js的模板如下
const state = {};
const getters = {};
const mutations = {};
const actions = {};
export default {
state,
getters,
mutations,
actions
}

从而实现,每个模块单独维护各自的数据状态

模块的局部状态

对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态state。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//模块user.js
// 这里的 `state` 对象是模块的局部状态
const getters = {
doubleCount (state) {
return state.count * 2
}
};
const mutations = {
increment (state) {
state.count++;
}
};

对于模块内部的 action,局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState:

1
2
3
4
5
6
7
actions: {
incrementIfOddOnRootSum ({ state, commit, rootState }) {
if ((state.count + rootState.count) % 2 === 1) {
commit('increment')
}
}
}

在组件中获取模块中的数据

1
2
3
4
5
//组件中
...mapState({
isLogin:state => state.moduleA.isLogin,
name:state => state.moduleB.name
})

导入封装的$request

1
2
3
//在入口文件 store/index.js
import request from '@/utils/request.js';
Vuex.Store.prototype.$request = request;

然后就能在vuex中直接使用this.$request()

-------------本文结束感谢您的阅读-------------