如果您的应用够简单,您最好不要使用 Vuex。一个简单的 store 模式 就足够您所需了。
但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。

(一) 简单状态管理

https://cn.vuejs.org/v2/guide/state-management.html#%E7%AE%80%E5%8D%95%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E8%B5%B7%E6%AD%A5%E4%BD%BF%E7%94%A8

接着我们继续延伸约定,组件不允许直接变更属于 store 实例的 state,而应执行 action 来分发 (dispatch) 事件通知 store 去改变,我们最终达成了 Flux 架构。这样约定的好处是,我们能够记录所有 store 中发生的 state 变更,同时实现能做到记录变更、保存状态快照、历史回滚/时光旅行的先进的调试工具。

<div id="app">
   <cpn></cpn>
    <cpn2></cpn2>
</div>

<template id="cpn">
    <div>
        <button @click="setMessage">cpn设置msg</button>
        <p>{{sourceOfTruth}}</p>
        <p>{{sharedState}}</p>
    </div>
</template>

<template id="cpn2">
    <div>
        <button @click="setMessage">cpn2设置msg</button>
        <p>{{sourceOfTruth}}</p>
        <p>{{sharedState}}</p>
    </div>
</template>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
    const store = {
        debug: true,
        state: {
            message: 'hello'
        },
        setMessageAction(newValue) {
            if (this.debug) console.log('setMessageAction triggered with', newValue)
            this.state.message = newValue
        },
        clearMessageAction() {
            if (this.debug) console.log('clearMessageAction triggered')
            this.state.message = ''
        }
    }

    const cpn = {
        template: "#cpn",
        data() {
            return {
                sourceOfTruth: 'cpn',
                sharedState: store.state
            }
        },
        methods: {
            setMessage(){
                store.setMessageAction('你好');
            }
        }
    }

    const cpn2 = {
        template: "#cpn2",
        data() {
            return {
                sourceOfTruth: 'cpn2',
                sharedState: store.state
            }
        },
        methods: {
            setMessage(){
                store.setMessageAction('hi');
            }
        }
    }

    const app = new Vue({
        el: "#app",
        components: {
            cpn,
            cpn2
        }
    });
</script>

(二) Vuex基础

  • 官方解释:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。

  • 什么时候使用Vuex
    多个组件依赖于同一状态;
    来自不同组件的行为需要变更同一状态;

  • https://vuex.vuejs.org/zh/

[1]. 安装Vuex插件

npm install vuex --save

[2]. 应用Vuex插件

  1. 在/src 目录下创建store目录,并且在store目录下新建index.js文件

    //引入Vue核心库
    import Vue from "vue";
    //引入Vuex
    import Vuex from "vuex";
    Vue.use(Vuex);
    
    //准备actions--用于响应组件中的动作
    const actions = {};
    //准备mutations--用于操作数据(state)
    const mutations = {};
    //准备state--用于存储数据
    const state = {};
    
    const store = new Vuex.Store({
        actions,
        mutations,
        state
    });
    
    export default store; //导出store
    
  2. 在main.js文件中

    //...
    import store from "./store"; //引入store
    //....
    
    new Vue({
      el: '#app',
      // 把 store 对象提供给 “store” 选项,这可以把 store 的实例注入所有的子组件
      store,//可以在其他页面使用$store调用
      render: h => h(App)
    };
    
  3. 在App.vue文件中

    <template>
      <div id="app">
        <hello-world></hello-world>
      </div>
    </template>
    
    <script>
     import HelloWorld from "@/components/HelloWorld";
    
    export default {
      name: 'App',
      components: {
        HelloWorld,
      }
    }
    </script>
    
  4. 在HelloWorld.vue文件中使用

    <template>
        <div>
          <p>这是HelloWorld文件</p>
          <h2>{{$store.state.score}}</h2>
        </div>
    </template>
    
    <script>
    export default {
      name: 'HelloWorld',
    }
    </script>
    

(三) 安装devtools(浏览器插件)

在这里插入图片描述

  1. devtools是浏览器插件需要
    进入谷歌网上应用店:https://chrome.google.com/webstore
    搜索:devtools

    在这里插入图片描述

  2. 重启浏览器
    在这里插入图片描述

(四) Vuex核心概念

[1]. Actions 异步操作

  1. Actions更倾向于整合多个mutation操作

  2. 若没有网络请求或其他业务逻辑,组件中可以越过actions,即不写dispatch,直接调用mutations中的方法。

  3. actions属性中的方法有一个默认的参数:context,context是和store对象具有相同方法和属性的对象,但是注意, 这里它们并不是同一个对象。所以可以使用contenxt.commit调用一个mutation中的方法或者通过context.state和context.getters来获取state的值和getters中的方法,当然也可以使用context.dispatch调用Actions中的方法。

  4. 在组件中使用this.$store.dispatch(‘actions中的方法’, ‘传递的参数’)来调用actions中的方法。

    <template>
        <p>学生信息:{{$store.dispath('getStuInfo')}}</p>
    </template >
    
    <script>
    	export default {
    	  methods: {
    	    asyncInfo(){
    	     this.$store.dispath('getStuInfo')
    	    },
    	  }
    	}
    </script>
    
  5. store.dispatch 可以处理被触发的 action 的处理函数返回的 Promise,并且 store.dispatch 仍旧返回 Promise:

    store.dispatch('actionA').then(() => {
      // ...
    })
    
  6. 代码
    /src/store/index.js:

    //引入Vue核心库
    import Vue from "vue";
    //引入Vuex
    import Vuex from "vuex";
    Vue.use(Vuex);
    
    const store = new Vuex.Store({
        state : {
            info :{
                name: '测试响应式',
                age: 52
            }
        },
        getters: {
        },
        mutations: {
            updateInfo(state, info){
                state.info.name = info;
            }
        },
        actions: {
            asyncInfo(context, info){
                setTimeout(()=>{
                    context.commit('updateInfo', info);
                }, 1000);
            }, 
            //actions 结合 promise
            asyncWithPromise(){
                return new Promise((resolve,reject)=>{
                    setTimeout(()=>{
                        resolve('异步成功');
                    }, 1000);
                });
            }
        }   
    });
    

    在HelloWorld.vue中添加

    <template>
        <div>
           <button @click="asyncInfo()">异步修改info</button>
           <button @click="asyncWithPromise()">异步结合promise</button>
        </div>
    </template>
    
    <script>
    export default {
      name: 'HelloWorld',
      methods: {
        asyncInfo(){
          this.$store.dispatch('asyncInfo', '异步测试代码');
        },
        asyncWithPromise(){
         this.$store.dispatch('asyncWithPromise', '异步测试代码').then((res)=>{console.log(res);});
        }
      }
    }
    </script>
    

[2]. Mutations 状态更新

  • ①. mutations中的方法是Vuex中唯一可以操作State的方式。
  • ②. mutations 中的方法默认有一个参数state
  • ③. 在组件中使用 commit 调用mutations 中的方法
    <!-- 组件模板中调用 -->
    <p>{{$store.commit('mutations中的方法名称', '参数');}}</p>
    <!-- 组件得配置对象中直接调用mutations中的方法(允许这么操作)-->
    this.$store.commit('mutations中的方法名称', '参数');
    
  • ④ mutation中的方法完成的事情尽可能单一
  • ⑤传递参数
    参数被称为是mutation的载荷(Payload)。多个参数时,使用对象进行传参。
    在HelloWorld.vue中添加
    <template>
       <div>
         <button @click="addTen()">score +10</button>
         <button @click="addNum(100)">score +num</button>
         <button @click="addStu()">add stu</button>
       </div>
    </template>
    
    <script>
    export default {
      name: 'HelloWorld',
      methods: {
        addTen(){
        	//没有参数,组件直接调用mutation中的scoreAddTen方法
          this.$store.commit('scoreAddTen');
        },
        addNum(num){
          this.$store.commit('scoreAddNum', num);
        },
        addStu(){
          this.$store.commit('addStu', {name:'孙七', age: 22});
        }
      }
    }
    </script>
    
    /src/store/index.js:
    const store = new Vuex.Store({
        mutations: {
            //1.不传递参数
            scoreAddTen(state){
                state.score += 10;
            },
            // 2.传递一个参数
            scoreAddNum(state, num){
                state.score += num;
            },
            // 3.用对象传递多个参数时
            addStu(state, info){
                state.students.push(info);
            }
        }
    });
    export default store;
    
    在组件中使用另一种风格 commit 调用mutations 中的方法,Mutation中的处理方式是将整个commit的对象作为payload使用。
    this.$store.commit({
    	type: "mutations中的事件名称",
    	"参数1": "参数值1",
    	"参数2": "参数值2",
    });
    
  • ⑥. Mutation同步函数和异步操作
    • 通常情况下, Vuex要求我们Mutation中的方法必须是同步方法.
      主要的原因是当我们使用devtools时, devtools可以帮助我们捕捉mutation的快照.
      但是如果是异步操作, 那么devtools将不能很好的追踪这个操作什么时候会被完成.
    • 通常情况下, 不要在mutation中进行异步的操作

[3]. State (单一状态树)

  1. 英文名称是Single Source of Truth (SSOT),也可以翻译成单一数据源。

  2. 所有状态要放在一个store,整个项目要只有一个store

  3. Vuex的store中的state是响应式的, 当state中的数据发生改变时, Vue组件会自动更新
    这就要求我们必须遵守一些Vuex对应的规则:
    1). 提前在store中初始化好所需的属性.

    2).当给state中的对象添加新属性时, 使用下面的方式:
    - 方式一: 使用Vue.set(‘被添加属性的对象’, ‘新属性名’, ‘属性值’)
    - 方式二: 用新对象给旧对象重新赋值

    3). 删除state中的对象属性
    Vue.delete(“对删除的属性对象”, “被删除的属性名”);

    4). 代码
    /src/store/index.js:

    ....
    
    const store = new Vuex.Store({
        state : {
            ...
            info :{
                name: '测试响应式',
                age: 52
            }
        },
        ....
        mutations: {
            ....
            // 给info添加属性
            addAttr(state){
                //下面代码可以添加message属性但是无法做到响应式
                // state.info.message = 'hello';
                Vue.set(state.info, 'message', 'hello');
            }
        }
    });
    

    在HelloWorld.vue中添加

    <template>
        <div>
          <h2>{{$store.state.info}}</h2>
          <button @click="addAttr()">给info添加一个属性</button>
        </div>
    </template>
    
    <script>
    export default {
      
      methods: {
        ....
        addAttr(){
          this.$store.commit('addAttr');
        }
      }
    }
    </script>
    

[4]. getters 获取状态

  1. getters相当于store的computed(计算属性),getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。

  2. getters中第一个的参数是state,这个参数就是store中的state属性,可以利用这个参数来获取store中state的属性值

  3. getters中第二个参数是getters,这个参数getters本身,可以利用这个参数来获取getters中的其他方法。

  4. getters默认是不能传递参数的, 如果希望传递参数, 那么只能让getters本身返回另一个函数。

  5. 案例:
    /src/store/index.js中定义了三个方法:

    import Vue from "vue";
    import Vuex from "vuex";
    
    Vue.use(Vuex);
    
    const store = new Vuex.Store({
        state : {
            score: 123,
            students: [
                {name: '张三', age: 18},
                {name: '李四', age: 19},
                {name: '王五', age: 20},
                {name: '李刘', age: 21}
            ]
        },
        getters: {
            //获取学生年龄大于19的人
            greaterAge19(state){
                return state.students.filter(stu => stu.age>19);
            },
            //获取学生年龄大于19的个数
            greaterAge19Length(state, getters){
                return getters.greaterAge19.length;
            },
            //获取学生年龄大于age的学生
            gettersAge(state){
                return function(age){
                    return state.students.filter(stu => stu.age>age);
                }
            }
        }
    });
    
    export default store;
    

    在HelloWorld.vue组件中

    <template>
        <div>
          <p>这是HelloWorld文件</p>
          <h2>{{$store.state.score}}</h2>
          <h2>{{$store.getters.greaterAge19}}</h2>
          <h2>{{$store.getters.greaterAge19Length}}</h2>
          <h2>{{$store.getters.gettersAge(20)}}</h2>
        </div>
    </template>
    
    <script>
    export default {
      name: 'HelloWorld',
    }
    </script>
    

[6]组件绑定的辅助函数mapState,mapGetter,mapMutations等

  • https://vuex.vuejs.org/zh/guide/state.html#mapstate-%E8%BE%85%E5%8A%A9%E5%87%BD%E6%95%B0
  • https://vuex.vuejs.org/zh/api/#%E7%BB%84%E4%BB%B6%E7%BB%91%E5%AE%9A%E7%9A%84%E8%BE%85%E5%8A%A9%E5%87%BD%E6%95%B0

①mapActions

  • mapActions方法:用于帮助我们生成与actions对话的方法,即:包含$store.dispatch(xxx)的函数
  • 若需传递参数需要在模板中绑定事件时就传递好参数,否则参数是事件对象。
    <template>
      <div>
        <button type="button" @click="multiply"></button>
        <!-- 将num作为参数传入 -->
        <button type="button" @click="multiply1(num)"></button>
         <!-- 如果不传参数默认传入event-->
        <button type="button" @click="cheng"></button>
      </div>
    </template>
    
    <script>
    import { mapState, mapMutations, mapActions } from "Vuex";
    export default {
      name: "App",
      data() {
        return {
          num: 1,
        };
      },
      methods: {
        multiply() {
          this.$store.dispatch("cheng", this.num);//调用actinos中的chengt方法并将num当做参数传递过去
        },
        //数组写法
        ...mapMutations([cheng]),//等同于 cheng(value){ this.$store.dispatch('cheng', value)}
        //对象写法
        ...mapMutations({ multiply1: "cheng" }),//等同于 multiply1(value){ this.$store.dispatch('cheng', value)}
      },
    };
    </script>
    

②mapMutations

  • mapMutations 方法:用于帮助我们生成与mutations对话的方法,即:包含$store.commit(xxx)的函数
  • 若需传递参数需要在模板中绑定事件时就传递好参数,否则参数是事件对象。
    <template>
      <div>
        <button type="button" @click="increment"></button>
        <!-- 将n作为参数传入 -->
        <button type="button" @click="increment1(n)"></button>
        <!-- 如果不传参数默认传入event-->
        <button type="button" @click="JIA"></button>
      </div>
    </template>
    
    <script>
    	import { mapMutations } from 'vuex'
    	
    	export default {
    		data(){
    			return {
    				n: 20
    			}
    		},
    		methods: {
    		  increment(){
    				this.$store.commit('JIA', this.n);//调用mutations中的JIA方法并将n当做参数传递过去
    			},
    			  //数组写法
    		    ...mapMutations(['JIA'])//等同于 JIA(value){ this.$store.commit('JIA', value)}
    			//对象写法
    			...mapMutations({increment1: 'JIA'}),//等同于 increment1(value){ this.$store.commit('JIA', value)}
    	  }
    	}
    </script>
    

③ mapState

  • mapState方法:用于帮助我们映射state中的数据为计算属性
    <template>
      <div>
        <p>计算结果为:{{sum1}}</p>
        <p>计算结果为:{{sum2}}</p>
        <p>计算结果为:{{sum}}</p>
      </div>
    </template>
    
    <script>
    import {mapState} from 'Vuex';
    export default {
      name: "App",
      computed:{
        sum1(){
          return this.$store.state.sum; //调用state中的sum
        },
          //数组写法
        ...mapState(['sum'])//等同于sum(){ return this.$store.state.sum}
        //对象写法
        ...mapState({sum2: 'sum'}), //等同于sum2(){ return this.$store.state.sum}
      },
    };
    </script>
    

④mapGetters

  • mapGetters方法:用于帮助我们映射getters中的数据为计算属性

    <template>
    	<div>
    	    <p>计算结果为:{{bigSum1}}</p>
    	    <p>计算结果为:{{bigSum}}</p>
    	    <p>计算结果为:{{bigSum2}}</p>
    	</div>
    </template>
    
    <script>
    	import { mapGetters } from 'vuex'
    	
    	export default {
    	  // ...
    	  computed: {
    	  bigSum1(){
    			return this.$store.getters.bigSum;//调用getters中的bigSum方法
    		},
    		//数组写法
    	    ...mapGetters(['bigSum']),//等同于bigSum(){ return this.$store.getters.bigSum}
    	    //对象写法
    	    ...mapGetters({bigSum2: 'bigSum'}),//等同于bigSum2(){ return this.$store.getters.bigSum}
    	  }
    	}
    </script>
    

(五). modules

[1]语法与解释

https://vuex.vuejs.org/zh/guide/modules.html

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:

  1. 将不同模块进行分割(/src/store/index.js)

    import Vue from "vue";
    import Vuex from "vuex";
    
    Vue.use(Vuex);
    
    //用户模块
    const userInfo = {
      namespaced: true, //开启命名空间
      state: {},
      actions: {},
      mutations: {},
      getters: {};
    
    const goodsInfo = {
      namespaced: true, //开启命名空间
      state: {},
      actions: {},
      mutations: {},
      getters: {}
    };
    
    export default new Vuex.Store({
      modules: {
        userAbout: userInfo, //当前模块的命名空间: 模块数据
        goodsAbout: goodsInfo
      }
    });
    
    
  2. 开启命名空间后,组件中读取state数据

    //方式一:this.$store.state.命名空间.state属性名
    this.$store.state.userAbout.userList
    //方式二:...mapState('命名空间', ['state中的属性名'])
     ...mapState('goodsAbout', ['goodsList', 'searchGoodsInfo']),
    
  3. 开启命名空间后,组件中调用getters中的方法:

    //方式一:this.$store.getters['命名空间/getters中的方法名']
    this.$store.getters['userAbout/userCount']
    //方式二:...mapGetters('命名空间', ['getters中的方法名'])
    ...mapGetters('goodsAbout', ['goodsCount'])
    
  4. 开启命名空间后,组件中调用actions中的方法

    //方式一:this.$store.dispatch('命名空间/方法名', 参数)
    this.$store.dispatch('personAbout/addPersonWang',person)
    //方式二:...mapActions('命名空间', {'当前组件希望使用的名字':'actions中的方法的名字'})
    ...mapActions('goodsAbout', {addGoods: 'addGoods'}),
    
  5. 开启命名空间后,组件中调用mutations中的方法

    //方式一://调用mutations中的方法:this.$store.commit('命名空间/方法名', 参数)
     this.$store.commit('userAbout/getUserInfoByName', this.userName)
    //方式二://调用Mutations的方法:...mapMutations('命名空间', {'当前组件希望使用的名字':'mutations中的方法的名字'})
    ...mapMutations('goodsAbout', {getGoodsInfo: 'getGoodsInfoByName'})
    

[2]用例

  1. vuex的存放位置:/src/store/index.js

    import Vue from "vue";
    import Vuex from "vuex";
    
    Vue.use(Vuex);
    
    const userInfo = {
      namespaced: true, //开启命名空间
      state: {
        userList: [],
        autoIncrementId: 0,
        searchUserInfo: null
      },
      actions: {
        addUser(context, name) {
          var userInfo = {
            id: context.state.autoIncrementId,
            name: name
          }
    
          context.commit('ADD_USER', userInfo);
          context.state.autoIncrementId ++;
        }
      },
      mutations: {
        ADD_USER(state, userInfo) {
          state.userList.push(userInfo);
        },
        getUserInfoByName(state, name) {
          for (const user of state.userList) {
            if (user.name === name) {
              state.searchUserInfo = user;
            }
          }
        }
      },
      getters: {
        userCount(state) {
          return state.userList.length
        }
      }
    };
    
    const goodsInfo = {
      namespaced: true, //开启命名空间
      state: {
        goodsList: [],
        autoIncrementId: 0,
        searchGoodsInfo: null
      },
      actions: {
        addGoods(context, name) {
          var goodsInfo = {
            id: context.state.autoIncrementId,
            name: name
          }
    
          context.commit('ADD_GOODS', goodsInfo);
          context.state.autoIncrementId ++;
        }
      },
      mutations: {
        ADD_GOODS(state, goodsInfo) {
          state.goodsList.push(goodsInfo);
        },
        getGoodsInfoByName(state, name) {
          for (const goods of state.goodsList) {
            if (goods.name === name) {
              state.searchGoodsInfo = goods;
            }
          }
        }
      },
      getters: {
        goodsCount(state) {
          return state.goodsList.length
        }
      }
    };
    
    export default new Vuex.Store({
      modules: {
        userAbout: userInfo, //命名空间: 数据
        goodsAbout: goodsInfo
      }
    });
    
  2. user组件 /src/components/User.vue

    <template>
      <div>
        <input type="text" v-model="userName" />
        <button @click="addUser">添加用户</button>
        <button @click="getUserInfo">查找用户</button>
        <p>用户总数:{{userCount}}</p>
    
        <p>用户列表</p>
        <ul>
          <li v-for="user in userList" :key="user.id">{{user.name}}</li>
        </ul>
    
         <p>查询到的商品信息:{{searchUserInfo}}</p>
      </div>
    </template>
    
    <script>
    
    export default {
      name: "User",
      data() {
        return {
          userName: "",
        };
      },
      computed:{
        userList(){
          //读取state数据:this.$store.state.命名空间.state属性名
          return this.$store.state.userAbout.userList
        },
        searchUserInfo(){
          //读取state数据
           return this.$store.state.userAbout.searchUserInfo
        },
        userCount(){
          //调用getters中的方法:this.$store.getters['命名空间/getters中的方法名']
          return this.$store.getters['userAbout/userCount']
        },
        
      },
      methods:{
        addUser(){
          //调用actions中的方法:this.$store.dispatch('命名空间/方法名', 参数)
          this.$store.dispatch('userAbout/addUser', this.userName);
        },
        getUserInfo(){
          //调用mutations中的方法:this.$store.commit('命名空间/方法名', 参数)
          this.$store.commit('userAbout/getUserInfoByName', this.userName)
        }
      }
    };
    </script>
    
  3. goods组件 /src/components/Goods.vue

    <template>
      <div>
        <input v-model="goodsName">
        <button @click="addGoods(goodsName)">添加商品</button>
        <button @click="getGoodsInfo(goodsName)">查询商品</button>
       
        <p>商品总数:{{goodsCount}}</p>
        <p>商品列表</p>
        <ul>
          <li v-for="goods in goodsList" :key="goods.id">{{goods.name}}</li>
        </ul>
        <p>查询到的商品信息:{{searchGoodsInfo}}</p>
      </div>
    </template>
    
    <script>
    import {mapState, mapGetters, mapActions, mapMutations} from 'vuex';
    
    
    export default {
      name:'Goods',
      data(){
        return {
          goodsName: ''
        }
      },
      computed:{
        //读取State的数据:...mapState('命名空间', ['state中的属性名'])
        ...mapState('goodsAbout', ['goodsList', 'searchGoodsInfo']),
         //调用getters的方法:...mapGetters('命名空间', ['getters中的方法名'])
        ...mapGetters('goodsAbout', ['goodsCount'])
      },
      methods:{
         //调用Actions的方法:...mapActions('命名空间', {'当前组件希望使用的名字':'actions中的方法的名字'})
        ...mapActions('goodsAbout', {addGoods: 'addGoods'}),
         //调用Mutations的方法:...mapMutations('命名空间', {'当前组件希望使用的名字':'mutations中的方法的名字'})
        ...mapMutations('goodsAbout', {getGoodsInfo: 'getGoodsInfoByName'})
      }
    }
    </script>
    
  4. App组件 /src/App.vue

    <template>
      <div>
        <Goods></Goods>
        <hr>
        <User></User>
      </div>
    </template>
    
    <script>
    import Goods from "./components/Goods.vue";
    import User from "./components/User.vue";
    
    export default {
      name: "App",
      components: { Goods, User },
    };
    </script>
    
Logo

前往低代码交流专区

更多推荐