如果你的项目里有很多页面(组件/视图),页面之间存在多级的嵌套关系,此时,这些页面假如都需要共享一个状态的时候,此时就会产生以下两个问题:
多个视图依赖同一个状态
来自不同视图的行为需要变更同一个状态
对于第一个问题,假如是多级嵌套关系,你可以使用父子组件传参进行解决,虽有些麻烦,但好在可以解决;对于兄弟组件或者关系更复杂组件之间,就很难办了,虽然可以通过各种各样的办法解决,可实在很不优雅,而且等项目做大了,代码就会变成屎山,实在令人心烦。
对于第二个问题,你可以通过父子组件直接引用,或者通过事件来变更或者同步状态的多份拷贝,这种模式很脆弱,往往使得代码难以维护,而且同样会让代码变成屎山。
把各个组件都需要依赖的同一个状态抽取出来,在全局使用单例模式进行管理。
在这种模式下,任何组件都可以直接访问到这个状态,或者当状态发生改变时,所有的组件都获得更新。
这就是 Vuex 背后的基本思想,借鉴了 Flux、Redux。与其他模式不同的是,Vuex 是专门为 Vue 设计的状态管理库,以利用 Vue.js 的细粒度数据响应机制来进行高效的状态更新。
接着,你就会看到下面这张官网的vuex使用周期图(看不懂没关系):
这个问题因人而异,如果你不需要开发大型的单页应用,此时你完全没有必要使用vuex,比如你的页面就两三个,使用vuex后增加的文件比你现在的页面还要多,那就没这个必要了。
假如你的项目达到了中大型应用的规模,此时您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。
进入项目,在命令行中输入安装指令,回车
npm install vuex --save
然后配置 vuex,使其工作起来:在src路径下创建store文件夹,然后创建index.js文件,文件内容如下:
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
// 定义一个name,以供全局使用
name: '张三',
// 定义一个number,以供全局使用
number: 0,
// 定义一个list,以供全局使用
list: [
{ id: 1, name: '111' },
{ id: 2, name: '222' },
{ id: 3, name: '333' },
]
},
});
export default store;
修改main.js:
import Vue from 'vue';
import App from './App';
import router from './router';
import store from './store'; // 引入我们前面导出的store对象
Vue.config.productionTip = false;
new Vue({
el: '#app',
router,
store, // 把store对象添加到vue实例上
components: { App },
template: ' '
});
最后修改App.vue:
<template>
<div></div>
</template>
<script>
export default {
mounted() {
// 使用this.$store.state.XXX可以直接访问到仓库中的状态
console.log(this.$store.state.name);
}
}
</script>
此时,启动项目 npm run dev
,即可在控制台输出刚才我们定义在store中的name的值。
官方建议我们以上操作this.$store.state.XXX
最好放在计算属性中,当然,我也建议你这么使用,这样可以让你的代码看起来更优雅一些,就像这样:
export default {
mounted() {
console.log(this.getName);
},
computed:{
getName() {
return this.$store.state.name;
}
},
}
此时可以得到和上面一样的效果。
是不是每次都写this.$store.state.XXX
让你感到厌烦,你实在不想写这个东西怎么办,当然有解决方案,就像下面这样:
<script>
import { mapState } from 'vuex'; // 从vuex中导入mapState
export default {
mounted() {
console.log(this.name);
},
computed: {
...mapState(['name']), // 经过解构后,自动就添加到了计算属性中,此时就可以直接像访问计算属性一样访问它
},
}
</script>
复制代码
你甚至可以在解构的时候给它赋别名,取外号,就像这样:
...mapState({ aliasName: 'name' }), // 赋别名的话,这里接收对象,而不是数组
当你看到这里的时候,证明你上一步已经完美的创建好一个vue项目,并且将vuex安装了进去!
好!接下来,我们介绍一个读取操作的 “修饰利器” —Getter
产品经理:所有的name前面都要加上“hello”!
我:为什么?
产品经理:我提需求还需要为什么吗?
我:好,我加!
这时候,你第一想到的是怎么加呢,emm…在每个页面上,使用this.$store.state.name
获取到值之后,进行遍历,前面追加"hello"即可。
♂️ 错!这样很不好,原因如下:
第一,假如你在A、B、C三个页面都用到了name,那么你要在这A、B、C三个页面都修改一遍,多个页面你就要加很多遍这个方法,造成代码冗余,很不好;
第二,假如下次产品经理让你把 “hello” 改成 “fuck” 的时候,你又得把三个页面都改一遍,这时候你只能抽自己的脸了…
吸取上面的教训,你会有一个新的思路:我们可以直接在store中对name进行一些操作或者加工,从源头解决问题!那么具体应该怎么写呢?这时候,本次将要介绍的这个Getter
利器闪亮登场!
首先,在store对象中增加getters属性
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
name: '张三',
number: 0,
list: [
{ id: 1, name: '111' },
{ id: 2, name: '222' },
{ id: 3, name: '333' },
]
},
// 在store对象中增加getters属性
getters: {
getMessage(state) { // 获取修饰后的name,第一个参数state为必要参数,必须写在形参上
return `hello${state.name}`;
}
},
});
export default store;
在组件中使用:
export default {
mounted() {
// 注意不是$store.state了,而是$store.getters
console.log(this.$store.state.name);
console.log(this.$store.getters.getMessage);
}
}
然后查看控制台:
官方建议:
是不是每次都写this.$store.getters.XXX
让你感到厌烦,你实在不想写这个东西怎么办,当然有解决方案,官方建议我们可以使用mapGetters
去解构到计算属性中,就像使用mapState
一样,就可以直接使用this
调用了,就像下面这样:
<script>
import { mapState, mapGetters } from 'vuex';
export default {
mounted() {
console.log(this.name);
console.log(this.getMessage);
},
computed: {
...mapState(['name']),
...mapGetters(['getMessage']),
},
}
</script>
此时可以得到和之前一样的效果。
当然,和mapState
一样你也可以取别名,取外号,就像下面这样:
...mapGetters({ aliasName: 'getMessage' }), // 赋别名的话,这里接收对象,而不是数组
-
OK,当你看到这里,你已经成功的把Getter用起来了,你也能明白在什么时候应该用到getters,你可以通过计算属性访问(缓存),也可以通过方法访问(不缓存),你甚至可以在getters的方法里面再调用getters方法,当然你也实现了像state那样,使用mapGetters解构到计算属性中,这样你就可以很方便的使用getters啦!
读取值的操作我们有 “原生读(state
)” 和 “修饰读(getters
)”,接下来就要介绍怎么修改值了!
Mutation
OK!首先恭喜你看到了这里,至此,我们已经成功访问到了store
里面的值,接下来我来介绍一下怎么修改state
里面的值。
说到修改值,有的同学就会想到这样写:
// 错误示范
this.$store.state.XXX = XXX;
修改store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
name: '张三',
number: 0,
},
mutations: { // 增加nutations属性
setNumber(state) { // 增加一个mutations的方法,方法的作用是让num从0变成5,state是必须默认参数
state.number = 5;
}
},
});
export default store;
修改App.vue
<script>
export default {
mounted() {
console.log(`旧值:${this.$store.state.number}`);
this.$store.commit('setNumber');
console.log(`新值:${this.$store.state.number}`);
},
}
</script>
修改store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
name: '张三',
number: 0,
},
mutations: {
setNumber(state) {
state.number = 5;
},
setNumberIsWhat(state, number) { // 增加一个带参数的mutations方法
state.number = number;
},
},
});
export default store;
修改App.vue
<script>
export default {
mounted() {
console.log(`旧值:${this.$store.state.number}`);
this.$store.commit('setNumberIsWhat', 666);
console.log(`新值:${this.$store.state.number}`);
},
}
</script>
运行项目,查看控制台:
注意:上面的这种传参的方式虽然可以达到目的,但是并不推荐,官方建议传递一个对象进去,这样看起来更美观,对象的名字你可以随意命名,但我们一般命名为payload,代码如下:
修改store/index.js
mutations: {
setNumber(state) {
state.number = 5;
},
setNumberIsWhat(state, payload) { // 增加一个带参数的mutations方法,并且官方建议payload为一个对象
state.number = payload.number;
},
},
修改App.vue
<script>
export default {
mounted() {
console.log(`旧值:${this.$store.state.number}`);
this.$store.commit('setNumberIsWhat', { number: 666 }); // 调用的时候也需要传递一个对象
console.log(`新值:${this.$store.state.number}`);
},
}
</script>
此时可以得到和之前一样的效果,并且代码更加美观!
这里说一条重要原则:Mutations里面的函数必须是同步操作,不能包含异步操作!(别急,后面会讲到异步)
这里说一条重要原则:Mutations里面的函数必须是同步操作,不能包含异步操作!(别急,后面会讲到异步)
这里说一条重要原则:Mutations里面的函数必须是同步操作,不能包含异步操作!(别急,后面会讲到异步)
好的,记住这个重要原则,我们再说一个小技巧:
官方建议:就像最开始的mapState
和mapGetters
一样,我们在组件中可以使用mapMutations
以代替this.$store.commit('XXX')
,是不是很方便呢?
<script>
import { mapMutations } from 'vuex';
export default {
mounted() {
this.setNumberIsWhat({ number: 999 });
},
methods: { // 注意,mapMutations是解构到methods里面的,而不是计算属性了
...mapMutations(['setNumberIsWhat']),
},
}
</script>
此时可以得到和之前一样的效果,并且代码又美观了一点!
当然你也可以给它叫别名,取外号,就像这样:
methods:{
...mapMutations({ setNumberIsAlias: 'setNumberIsWhat' }), // 赋别名的话,这里接收对象,而不是数组
}
OK,关于Mutation
的介绍大致就是这样,另外你也掌握了在定义mutations
方法的时候有无参数应该怎么写;并且听取了官方建议,使用mapMutations解构到你的组件内部的methods
里,这样你就可以很简单的使用mutations
方法啦!
上面提到,Mutations
只能进行同步操作,所以,我们马上开始下一节,看看使用Actions
进行异步操作的时候应该注意什么!
OK!本节我们来学习使用Actions
,Actions
存在的意义是假设你在修改state
的时候有异步操作,vuex作者不希望你将异步操作放在Mutations
中,所以就给你设置了一个区域,让你放异步操作,这就是Actions
我们直接上一个代码
修改store/index.js
const store = new Vuex.Store({
state: {
name: '张三',
number: 0,
},
mutations: {
setNumberIsWhat(state, payload) {
state.number = payload.number;
},
},
actions: { // 增加actions属性
setNum(content) { // 增加setNum方法,默认第一个参数是content,其值是复制的一份store
return new Promise(resolve => { // 我们模拟一个异步操作,1秒后修改number为888
setTimeout(() => {
content.commit('setNumberIsWhat', { number: 888 });
resolve();
}, 1000);
});
}
}
});
修改App.vue
async mounted() {
console.log(`旧值:${this.$store.state.number}`);
await this.$store.dispatch('setNum');
console.log(`新值:${this.$store.state.number}`);
},
运行项目,查看控制台:
看了例子,是不是明白了,action
就是去提交mutation
的,什么异步操作都在action
中消化了,最后再去提交mutation
的。
当然,你可以模仿mutation
进行传参,就像下面这样:
修改store/index.js
actions: {
setNum(content, payload) { // 增加payload参数
return new Promise(resolve => {
setTimeout(() => {
content.commit('setNumberIsWhat', { number: payload.number });
resolve();
}, 1000);
});
},
}
修改App.vue
async mounted() {
console.log(`旧值:${this.$store.state.number}`);
await this.$store.dispatch('setNum', { number: 611 });
console.log(`新值:${this.$store.state.number}`);
},
运行项目,查看控制台
没有任何问题!
官方建议1:你如果不想一直使用this.$store.dispatch('XXX')
这样的写法调用action
,你可以采用mapActions
的方式,把相关的actions
解构到methods
中,用this
直接调用:
<script>
import { mapActions } from 'vuex';
export default {
methods: {
...mapActions(['setNum']), // 就像这样,解构到methods中
},
async mounted() {
await this.setNum({ number: 123 }); // 直接这样调用即可
},
}
</script>
当然,你也可以取别名,取外号,就像下面这样:
...mapActions({ setNumAlias: 'setNum' }), // 赋别名的话,这里接收对象,而不是数组
官方建议2:在store/index.js
中的actions
里面,方法的形参可以直接将commit
解构出来,这样可以方便后续操作:
actions: {
setNum({ commit }) { // 直接将content结构掉,解构出commit,下面就可以直接调用了
return new Promise(resolve => {
setTimeout(() => {
commit('XXXX'); // 直接调用
resolve();
}, 1000);
});
},
},
action
在vuex
的位置了吧,什么时候该用action
,什么时候不用它,你肯定有了自己的判断,最主要的判断条件就是我要做的操作是不是异步,这也是action
存在的本质。当然,你不要将action
和mutation
混为一谈,action
其实就是mutation
的上一级,在action
这里处理完异步的一些操作后,后面的修改state
就交给mutation
去做了。好!大致对vuex的讲解就到这里了,看到这里你肯定对vuex不陌生了,你会安装它,配置它,读取state的值,甚至修饰读(Getter),然后你会修改里面的值了(Mutation),假如你有异步操作并且需要修改state,那你就要使用Action,这样,你就可以在你的项目中用起来vuex啦!加油吧!
作者:三年没洗澡
链接:https://juejin.cn/post/6928468842377117709
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。