vue中关于如何正确使用vuex(超超超详解)
vue中关于如何正确使用vuex(超超超详解)
第一步:vuex是什么
Vuex 是一个专为 Vue.js 应用程序开发的
状态管理模式
。它采用集中式存储管理
应用的所有组件的状态,并以相应的规则保证状态以一种可预测
的方式发生变化。
第二步:了解vuex
-
何时使用
如果你不打算构建大型项目的单页应用,使用Vuex可能会变得很繁琐,对于大型项目,可以使用Vuex作为不同组件之间的状态管理,而对于小型的项目,推荐使用HTML5特有的属性,
localStroage
和sessionStroage
作为数据之间的传递就可以。 -
应用场景如下 :
如果你的项目里有很多页面(组件/视图),并且页面之间存在多级的嵌套关系,此时,这些页面假如都需要共享一个状态的时候,此时就会存在以下两个问题:
👿.
多个(组件/视图)依赖同一个状态
👿.
来自不同(组件/视图)的行为需要变更同一个状态
-
那怎么解决呢(这里先不要考虑vuex) :
❤️:可以使用父子传参以及bus传参还有路由传参一系列的解决办法,但是如果多级嵌套的话,那就意味着你可能要写很多的重复代码,而且如果项目是大项目,一旦公用状态发生改变,那后期维护起来可能会很麻烦。
❤️:可以通过父子组件传参直接引用,或者根据事件变更同步此状态,然后多份copy,但是这种模式很脆弱,同样后期维护起来可能会很麻烦。
-
那以上两种方案均不可行,这里就需要考虑使用vuex 的思路解决此问题:
😃. 我们可以考虑有没有一种机制,可以将组件/视图依赖的这同一个状态给存起来,然后全局使用单例模式进行管理
😃. 这种机制,任何组件都可以访问这个同一状态,或者当此状态发生改变时,所以组件都获得更新
-
这时候,Vuex诞生了!
这就是 Vuex 背后的基本思想,借鉴了 Flux、Redux。与其他模式不同的是,Vuex 是专门为 Vue 设计的状态管理库,以利用 Vue.js 的细粒度数据响应机制来进行高效的状态更新
第三步 : 安装vuex
npm install vuex --save
第四步 :初始化配置vuex
-
这里我是直接创建了一个新的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>
-
😃 执行npm run serve启动项目,结果如图:
-
😃 你甚至可以在解构的时候给它赋别名,取外号,就像这样
...mapState({ name: 'userName',age:'userAge' }), // 赋别名的话,这里接收对象,而不是数组
第五步: 了解读取修饰器 - Getter
-
😈 加入现在你讲userName属性已经取出来并且展示在所有的页面上了,但是现在项目经理突然告诉你说在人员模块取的name前面需要拼接一下 ‘hello’
-
😈 这时候,你第一想到的是怎么加呢,emm… 在需要修改的每个页面上,使用this.$store.state.userName 获取到值之后,进行遍历,前面追加"hello"即可。
解决是解决了,但是这样处理效果很不好,原因如下: -
😈 假如你在A、B、C三个页面都用到了name,那么你要在这A、B、C三个页面都修改一遍,多个页面你都需要修改,造成代码冗余
-
😈 如果下次项目经理让你把 “hello” 改成 “fuck” 的时候,你又得把三个页面都改一遍,啊这就。。。。。。
-
❤️ 那既然我们介绍到了getter,就得考虑如何用getter修改此问题:
-
❤️ 首先,在index.js中添加getters属性
export default new Vuex.Store({ state: { userName:'admin', userAge:'24', }, // 在store对象中增加getters属性 getters: { getName(state) { return `hello${state.userName}`; }, }, })
-
❤️ 获取修改之后的值,这里我们还是直接采取mapGetters的方法获取
<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)
,接下来就要介绍怎么修改值了!
第六步:如何修改值 - Mutation
-
👿 如果要修改store中的变量,不能直接 this.$store.state.xxx=xxxx 修改,这是错误的,因为在vuex中,我们不能直接修改仓库里的值,必须用vuex自带的方法去修改,这时候就得用 mutations
-
😃 mutations 的原理是用来触发事件,相当于方法。用户需要通过触发这个方法,借此来保存数据,参数的话,第二个参数就是用户传入的值,然后在方法中赋值给state中的变量
-
❤️ 假如我们现在有一个需求:用户登录进来之后,我们需要将用户名获取并且保存,然后相关接口传参的时候都得将获得的用户名传值过去。
-
😃 首先,index.js代码如下
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
-
😃 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>
-
❤️ 以上两种方法均可以,实现效果一致,然后运行项目查看控制台输出如图:
-
❤️ ❤️ ❤️ 至此,整个vuex基本概念加如何使用已总结完毕。 这里附上vuex的运行过程,官网的图片:
-
😃
组件派发任务到actions,actions触发mutations中的方法,然后mutations来改变state中的数据,数据变更后响应推送给组件,组件重新渲染
😃 -
❤️ 接下来想象一下,以上介绍的store/index.js里面的内容是非常少的,如果你是一个稍微有些规格的项目,那么你将会得到一个成百上千行的index.js,然后查找修改一些东西就会非常费劲,因此我们考虑一下如何优化store。这里常用的方案有两种:
第八步:按照属性拆分index.js
-
😃 正如我们所知道的store对象中包含四个属性,如图:
-
😃 因此,我们可以考虑在store文件夹下创建四个对应的js文件夹:
-
❤️ 拆出来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 }); }, }
-
😃 然后将以上四个文件在组装到主文件index.js里面
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中的变量时,还是按照上面的方式获取调用。
第九步:按功能模块进行拆分 - Module+命名空间的概念
-
😃 假如我们现在有一个商品模块,但是刚才的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.模块名.属性名})
- 在methods中调用
this.$store.state.模块名.模块属性
- 插值表达式使用 :
-
😃
getters
- 插值表达式使用 :
$store.getter.[模块名/模块属性]
- 在methods中调用:
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中的方法名'] (参数)
- 触发方法 :
更多推荐
所有评论(0)