vue中关于如何正确使用vuex(超超超详解)

第一步:vuex是什么

Vuex 是一个专为 Vue.js 应用程序开发的 状态管理模式。它采用 集中式存储管理 应用的所有组件的状态,并以相应的规则保证状态以一种可预测 的方式发生变化。

第二步:了解vuex

  • 何时使用

    如果你不打算构建大型项目的单页应用,使用Vuex可能会变得很繁琐,对于大型项目,可以使用Vuex作为不同组件之间的状态管理,而对于小型的项目,推荐使用HTML5特有的属性,localStroagesessionStroage 作为数据之间的传递就可以。

  • 应用场景如下 :

    如果你的项目里有很多页面(组件/视图),并且页面之间存在多级的嵌套关系,此时,这些页面假如都需要共享一个状态的时候,此时就会存在以下两个问题:

    . 多个(组件/视图)依赖同一个状态

    . 来自不同(组件/视图)的行为需要变更同一个状态

  • 那怎么解决呢(这里先不要考虑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的值,如图:
    vue中关于如何正确使用vuex(超超超详解)_第1张图片

  • 当然官网上针对获取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启动项目,结果如图:
    vue中关于如何正确使用vuex(超超超详解)_第2张图片

  • 你甚至可以在解构的时候给它赋别名,取外号,就像这样

    ...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>
    
  • 然后查看控制台的输出:

    vue中关于如何正确使用vuex(超超超详解)_第3张图片

  • 好了, 至此读取值的操作我们有 原生读(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>
    
  • 以上两种方法均可以,实现效果一致,然后运行项目查看控制台输出如图:
    vue中关于如何正确使用vuex(超超超详解)_第4张图片

  • 且记: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>
    
  • ❤️ 以上两种方法均可以,实现效果一致,然后运行项目查看控制台输出如图:
    vue中关于如何正确使用vuex(超超超详解)_第5张图片

  • ❤️ ❤️ ❤️ 至此,整个vuex基本概念加如何使用已总结完毕。 这里附上vuex的运行过程,官网的图片:
  • 组件派发任务到actions,actions触发mutations中的方法,然后mutations来改变state中的数据,数据变更后响应推送给组件,组件重新渲染

    vue中关于如何正确使用vuex(超超超详解)_第6张图片

  • ❤️ 接下来想象一下,以上介绍的store/index.js里面的内容是非常少的,如果你是一个稍微有些规格的项目,那么你将会得到一个成百上千行的index.js,然后查找修改一些东西就会非常费劲,因此我们考虑一下如何优化store。这里常用的方案有两种:

第八步:按照属性拆分index.js

  • 正如我们所知道的store对象中包含四个属性,如图:
    vue中关于如何正确使用vuex(超超超详解)_第7张图片

  • 因此,我们可以考虑在store文件夹下创建四个对应的js文件夹:
    vue中关于如何正确使用vuex(超超超详解)_第8张图片

  • ❤️ 拆出来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>
    
  • ❤️ 查看控制台输出:
    vue中关于如何正确使用vuex(超超超详解)_第9张图片

  • 到这里,模块化开发的基本实现算是总结完了,但是这里有个属性 namespaced,它的取值会影响我们如何获取对应模块的变量以及方法,因此以下对此属性做一个简单的概述。

  • namespaced: true 保证内部模块的高封闭性;

  • ❤️ 命名空间的概念:

  • ❤️ 默认情况下,模块内部的 action、mutation 和 getter 是 注册在全局命名空间 的, 可以直接调用(除了访问state以及getter中内容需要加模块名,其他访问模块中的内容直接访问,不需要加模块名), 这样一来,不仅容易和其他模块, 同名state或者函数发生冲突,也很难让人直观得看出具体是在哪个子模块调用的。

  • 所以在子模块的配置项目中, 必须添加 namespaced: true 属性来开启命名空间, 便于区分和其他模块及主模块中的同名状态或者函数, 防止冲突,开启后需要访问子模块中的内容就需要带模块名。

  • 开启命名空间后如何使用?

  • ❤️ state

    1. 插值表达式使用 :$store.state.模块名.模块属性
    2. 映射为辅助函数 - 数组格式: ...mapState('模块名', ['属性名'])
    3. 映射为辅助函数 - 对象格式: ...mapState({属性名:state=>store.模块名.属性名})
    4. 在methods中调用 this.$store.state.模块名.模块属性
  • getters

    1. 插值表达式使用 : $store.getter.[模块名/模块属性]
    2. 在methods中调用:this.$store.getters.[模块名/模块属性]
    3. 映射为辅助函数 - 数组格式: ...mapGetters('模块名', ['属性名'])
    4. 注意:这里是通过[模块名/模块属性]获取,和上面的state是有区别的 !!
  • ❤️ mutations

    1. 触发方法 :this.$store.commit('模块名/mutations中的方法名', '实参')
    2. 映射为辅助函数 - 数组格式: ...mapMutations(['模块名/mutations中的方法名'])
    3. 映射为辅助函数 - 方法调用 :this['模块名/mutations中的方法名'] (参数)
  • actions

    1. 触发方法 : this.$store.dispatch('模块名/actions中的方法名', '实参')
    2. 在标签的事件上(如点击、滑动)触发 :$store.dispatch('模块名/actions中的方法名', '实参')
    3. 映射为辅助函数 - 数组格式:...mapActions(['模块名/actions中的方法名'])
    4. 映射为辅助函数 - 方法调用 :this['模块名/actions中的方法名'] (参数)

你可能感兴趣的:(vue,web前端,JavaScript学习笔记,vue.js,javascript,前端)