Vuex 是一个专为 Vue.js 应用程序开发的
状态管理模式
。它采用集中式存储管理
应用的所有组件的状态,并以相应的规则保证状态以一种可预测
的方式发生变化。
如果你不打算构建大型项目的单页应用,使用Vuex可能会变得很繁琐,对于大型项目,可以使用Vuex作为不同组件之间的状态管理,而对于小型的项目,推荐使用HTML5特有的属性,
localStroage
和sessionStroage
作为数据之间的传递就可以。
如果你的项目里有很多页面(组件/视图),并且页面之间存在多级的嵌套关系,此时,这些页面假如都需要共享一个状态的时候,此时就会存在以下两个问题:
. 多个(组件/视图)依赖同一个状态
. 来自不同(组件/视图)的行为需要变更同一个状态
❤️:可以使用父子传参以及bus传参还有路由传参一系列的解决办法,但是如果多级嵌套的话,那就意味着你可能要写很多的重复代码,而且如果项目是大项目,一旦公用状态发生改变,那后期维护起来可能会很麻烦。
❤️:可以通过父子组件传参直接引用,或者根据事件变更同步此状态,然后多份copy,但是这种模式很脆弱,同样后期维护起来可能会很麻烦。
. 我们可以考虑有没有一种机制,可以将组件/视图依赖的这同一个状态给存起来,然后全局使用单例模式进行管理
. 这种机制,任何组件都可以访问这个同一状态,或者当此状态发生改变时,所以组件都获得更新
这时候,Vuex诞生了!
这就是 Vuex 背后的基本思想,借鉴了 Flux、Redux。与其他模式不同的是,Vuex 是专门为 Vue 设计的状态管理库,以利用 Vue.js 的细粒度数据响应机制来进行高效的状态更新
npm install vuex --save
这里我是直接创建了一个新的vue项目,所以默认关于store文件夹下面的index.js文件以及main.js文件已经初始化配置完成,这里不做描述。直接使用:
❤️ store/index.js:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
// 定义一个userName属性,供全局使用
userName:'admin',
// 定义一个userAge属性,供全局使用
userAge:'24',
},
mutations: {},
actions: {},
modules: {}
})
然后home.vue获取userName属性:
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png">
</div>
</template>
<script>
export default {
name: 'Home',
mounted() {
// 使用this.$store.state.XXX可以直接访问到仓库中的状态
console.log('userName------',this.$store.state.userName);
},
}
</script>
❤️ 执行npm run serve启动项目,此时可以在控制台输出刚才我们定义在store中的userName的值,如图:
当然官网上针对获取store中的state下的全局属性,最好放在computed计算属性中,因此我们也可以这样实现
export default {
name: 'Home',
computed:{
getUserName(){
return this.$store.state.userName
}
},
mounted() {
console.log('计算属性获取 userName -------',this.getUserName)
},
}
此时可以得到和上面一样的执行结果。
❤️ 如果需要获取userName以及userAge这两个属性,那么重复写两遍this.$store.state.xxx略显麻烦,这里我们可以采用mapState
<script>
import { mapState } from 'vuex'; // 从vuex中导入mapState
export default {
name: 'Home',
computed:{
// 经过解构后,自动就添加到了计算属性中,此时就可以直接像访问计算属性一样访问它
...mapState(['userName','userAge'])
},
mounted() {
console.log('userName -------',this.userName)
console.log('userAge -------',this.userAge)
},
}
</script>
你甚至可以在解构的时候给它赋别名,取外号,就像这样
...mapState({ name: 'userName',age:'userAge' }), // 赋别名的话,这里接收对象,而不是数组
加入现在你讲userName属性已经取出来并且展示在所有的页面上了,但是现在项目经理突然告诉你说在人员模块取的name前面需要拼接一下 ‘hello’
这时候,你第一想到的是怎么加呢,emm… 在需要修改的每个页面上,使用this.$store.state.userName 获取到值之后,进行遍历,前面追加"hello"即可。
解决是解决了,但是这样处理效果很不好,原因如下:
假如你在A、B、C三个页面都用到了name,那么你要在这A、B、C三个页面都修改一遍,多个页面你都需要修改,造成代码冗余
如果下次项目经理让你把 “hello” 改成 “fuck” 的时候,你又得把三个页面都改一遍,啊这就。。。。。。
export default new Vuex.Store({
state: {
userName:'admin',
userAge:'24',
},
// 在store对象中增加getters属性
getters: {
getName(state) {
return `hello${state.userName}`;
},
},
})
<script>
import { mapState,mapGetters } from 'vuex'; // 从vuex中导入mapState,mapGetters
export default {
name: 'Home',
computed:{
// 经过解构后,自动就添加到了计算属性中,此时就可以直接像访问计算属性一样访问它
...mapState(['userName','userAge']),
...mapGetters(['getName'])
},
mounted() {
console.log('userName -------',this.userName)
console.log('userAge -------',this.userAge)
console.log('拼接 hello ------ ',this.getName)
//console.log(this.$store.getters.getName); 也可以这样直接获取
},
}
</script>
然后查看控制台的输出:
原生读(state)” 和 “修饰读(getters)
,接下来就要介绍怎么修改值了!如果要修改store中的变量,不能直接 this.$store.state.xxx=xxxx 修改,这是错误的,因为在vuex中,我们不能直接修改仓库里的值,必须用vuex自带的方法去修改,这时候就得用 mutations
mutations 的原理是用来触发事件,相当于方法。用户需要通过触发这个方法,借此来保存数据,参数的话,第二个参数就是用户传入的值,然后在方法中赋值给state中的变量
❤️ 假如我们现在有一个需求:用户登录进来之后,我们需要将用户名获取并且保存,然后相关接口传参的时候都得将获得的用户名传值过去。
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
userName:'',
},
mutations: {
getUserName(state,data){//第一个参数是整个state对象,第二个参数就是用户传入的变量
state.userName=data.userNamee)
}
},
})
对应home.vue在获得用户名之后调用getUserName方法将值保存在vuex,这里有两种方案可以调用mutations中的方法
❤️ 第一种: this.$store.commit(‘对应方法名’,‘需要保存的用户名’)
<script>
export default {
name: 'Home',
mounted() {
this.getLogin()// 假设页面进来就执行点击事件
console.log('新值 username ------ ',this.$store.state.userName)
},
methods:{
// 假设点击完登录按钮,登录成功之后就可以获取userName
getLogin(){
//具体代码省略....
this.$store.commit('getUserName',{userName:'0817'})
}
}
}
</script>
❤️ 第二种:利用mapMutations引入,只不过这里不再是解构在计算属性中,而是直接将方法映射在methods中
<script>
import { mapMutations } from 'vuex'; // 从vuex中导入mapMutations
export default {
name: 'Home',
mounted() {
this.getLogin()// 假设页面进来就执行点击事件
console.log('新值 username ------ ',this.$store.state.userName)
},
methods:{
// 这里注意一下,mapMutations是解构到methods方法里面,而不是计算属性了
...mapMutations(['getUserName']),
// 假设点击完登录按钮,登录成功之后就可以获取userName
getLogin(){
//具体代码省略....
this.getUserName({userName:'0817'})
}
}
}
</script>
且记:Mutations里面的函数必须是同步操作,不能包含异步操作!
那总结到这里,mutations算是总结完了,但是刚才提醒了Mutations里面的函数必须是同步操作,那什么可以进行异步呢,这里,就得引出action
Actions存在的意义是假设你在修改state的时候有异步操作,vuex作者不希望你将异步操作放在Mutations中,所以就给你设置了一个区域,让你放异步操作,这就是Actions
假设现在我们模拟一个异步操作
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
userName:'0817',
},
mutations: {
getUserName(state,data){//第一个参数是整个state对象,第二个参数就是用户传入的变量
state.userName=data.userName
}
},
actions: {
setName(content,payload) {
// 增加setName方法,默认第一个参数是content,其值是复制的一份store,第二个参数是要传的值
return new Promise(resolve => {
content.commit('getUserName',payload);
resolve();
});
},
},
})
同理,也有两种方式可以调用action中的方法
第一种:直接利用**this.$store.dispatch(‘对应action中药调用的方法名’,‘payload参数’) **
<script>
export default {
name: 'Home',
mounted() {
console.log('旧值 username ------ ',this.$store.state.userName)
this.$store.dispatch('setName',{userName:'csAdmin'})
console.log('新值 username ------ ',this.$store.state.userName)
},
}
</script>
第二种:利用mapActions引入,只不过这里不再是解构在计算属性中,而是直接将方法映射在methods中
<script>
import { mapActions} from 'vuex'; // 从vuex中导入mapMutations
export default {
name: 'Home',
methods:{
...mapActions(['setName'])
}
mounted() {
console.log('旧值 username ------ ',this.$store.state.userName)
this.setName({userName:'csAdmin'})
console.log('新值 username ------ ',this.$store.state.userName)
},
}
</script>
组件派发任务到actions,actions触发mutations中的方法,然后mutations来改变state中的数据,数据变更后响应推送给组件,组件重新渲染
❤️ 接下来想象一下,以上介绍的store/index.js里面的内容是非常少的,如果你是一个稍微有些规格的项目,那么你将会得到一个成百上千行的index.js,然后查找修改一些东西就会非常费劲,因此我们考虑一下如何优化store。这里常用的方案有两种:
❤️ 拆出来state放在state.js中
export const state = {
userName: '0817',
};
❤️ 拆出来getters放在getters.js中
export const getters = {
getName(state) {
return `hello${state.userName}`;
},
};
❤️ 拆出来mutations放到mutations.js中:
export const mutations = {
getUserName(state,data){
state.userName=data.userName
}
};
❤️ 拆出来actions放到actions.js中:
export const actions = {
setName(content,payload) {
return new Promise(resolve => {
content.commit('getUserName', payload);
resolve
});
},
}
import Vue from 'vue'
import Vuex from 'vuex'
import { state } from './state'; // 引入 state
import { getters } from './getters'; // 引入 getters
import { mutations } from './mutations'; // 引入 mutations
import { actions } from './actions'; // 引入 actions
Vue.use(Vuex)
export default new Vuex.Store({
state: state,
getters: getters,
mutations: mutations,
actions: actions
})
❤️ 然后对应的页面调用或者获取state中的变量时,还是按照上面的方式获取调用。
假如我们现在有一个商品模块,但是刚才的store中存的都是user相关的信息,如果再加入商品对应的信息,难免有点不好管理,这样我们可以直接考虑增加商品模块
❤️ 创建store2.js模块
export const store2 ={
namespaced:true,//为当前模块开启独立的命名空间
state:{
money:''
},
getters:{},
mutations:{
getMoney(state,data){
state.money=data.money
}
},
actions:{}
}
❤️ 然后在index.js中引入我们新创建的store2模块:
import Vue from 'vue'
import Vuex from 'vuex'
import { state } from './state'; // 引入 state
import { getters } from './getters'; // 引入 getters
import { mutations } from './mutations'; // 引入 mutations
import { actions } from './actions'; // 引入 actions
import {store2} from './store2'; // 引入store2模块
Vue.use(Vuex)
export default new Vuex.Store({
state: state,
getters: getters,
mutations: mutations,
actions: actions,
modules:{
store2
}
})
❤️ 获取模块2中对应的变量
<script>
import { mapState,mapMutations } from 'vuex';
export default {
name: 'Home',
computed:{
...mapState({
money:state=>state.store2.money
}),
},
methods:{
...mapMutations(['store2/getMoney'])
},
mounted() {
console.log('旧值 money ------ ',this.money)
this['store2/getMoney']({money:'8989'})
console.log('新值 money ------ ',this.money)
},
}
</script>
到这里,模块化开发的基本实现算是总结完了,但是这里有个属性 namespaced,它的取值会影响我们如何获取对应模块的变量以及方法,因此以下对此属性做一个简单的概述。
namespaced: true 保证内部模块的高封闭性;
❤️ 命名空间的概念:
❤️ 默认情况下,模块内部的 action、mutation 和 getter 是 注册在全局命名空间
的, 可以直接调用(除了访问state以及getter中内容需要加模块名,其他访问模块中的内容直接访问,不需要加模块名
), 这样一来,不仅容易和其他模块, 同名state或者函数发生冲突,也很难让人直观得看出具体是在哪个子模块调用的。
所以在子模块的配置项目中, 必须添加 namespaced: true 属性来开启命名空间, 便于区分和其他模块及主模块中的同名状态或者函数, 防止冲突,开启后需要访问子模块中的内容就需要带模块名。
开启命名空间后如何使用?
❤️ state
$store.state.模块名.模块属性
...mapState('模块名', ['属性名'])
...mapState({属性名:state=>store.模块名.属性名})
this.$store.state.模块名.模块属性
getters
$store.getter.[模块名/模块属性]
this.$store.getters.[模块名/模块属性]
...mapGetters('模块名', ['属性名'])
[模块名/模块属性]
获取,和上面的state是有区别的 !!❤️ mutations
this.$store.commit('模块名/mutations中的方法名', '实参')
...mapMutations(['模块名/mutations中的方法名'])
this['模块名/mutations中的方法名'] (参数)
actions
this.$store.dispatch('模块名/actions中的方法名', '实参')
$store.dispatch('模块名/actions中的方法名', '实参')
...mapActions(['模块名/actions中的方法名'])
this['模块名/actions中的方法名'] (参数)