Vuex的介绍和使用

vuex是什么?

  • Vuex 是一个专为Vue.js应用程序开发的状态管理库,可以用于各种复杂组件之间的通信,属于vue全家桶相关的生态工具链

什么情况下需要使用vuex?

  • 如果开发过程中存在各种组件或者很多复杂的组件,且各种组件之间需要进行相互通信(个人认为即如果把某一个页面拆成各种小组件时,便需要考虑使用vuex进行通讯,提升组件通信间的开发效率)

如果不使用Vuex通信还能使用什么?

  • 父子组件通讯
  • 兄弟组件通信
  • 隔代组件通讯

vuex的工作流程

  • 图示如下:
image.png
  • 工作流程文字描述如下:
  1. vue组件里面,通过dispatch来触发actions提交修改数据的操作。
  2. 然后再通过actionscommit来触发mutations来修改数据。
  3. mutations接收到commit的请求,就会自动通过Mutate来修改state里面的值。
  4. 最后由store触发每一个调用它的组件的更新

如何使用Vuex

  1. 使用 vue-cli 生成项目时,可以选择集成 vuex 到项目中。此时, vue-cli 会自动安装 vuex ,并在 src 文件夹下生成store文件下的 store.js 完成 vuex 的引入和配置。

  2. 手动引入安装

    npm install vuex 
    //或者
    yarn add vuex
    
  3. (备注)如果是vue-cli创建的项目,且在最开始集成了vuex则不需要进行如下配置,否则请参考如下配置

  4. image.png
  1. image.png

vuex的五大核心要素

Start

  1. state 表示状态,类似于 vue 中的data,用来存放状态值,里面存放的数据是响应式的
// 代码参考

// store.js
import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    text: "hello word",
  },
  getters: {},
  mutations: {},
  actions: {},
  modules: {},
});

//某组件代码(其他页面访问方式相同)
<template>
  <div>
    <h2>我是某组件代码</h2>
    //页面通过 $store.state.text 可以获取相关值
    <h3>这是vuex中的数据:{{ $store.state.text }}</h3>
  </div>
</template>

<script>
export default {
  name: "allHome",
  methods: {
    getVuex() {
        //这里通过 this.$store.state.text 可以获取相关值
      console.log("这是vuex中的数据:" + this.$store.state.text);
    },
  },
  mounted() {
    this.getVuex();
  },
};
</script>

<style></style>

Getter

  1. 类似于 Vue 中的 计算属性(可以认为是 store 的计算属性),getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算
// 代码参考  ... 表示省略这里的代码(因为这里代码是相同的),省略部分请参考对照上方代码

// store.js
....
  state: {
    students: [
      { id: 0, name: "张三", age: 25 },
      { id: 1, name: "李四", age: 30 },
      { id: 2, name: "王二", age: 19 },
      { id: 3, name: "麻子", age: 18 },
    ],
  },
  getters: {
    greaterAgeCount: (state) => {
      return state.students.filter((s) => s.age > 20).length;
    },
  },
 //也可以采取下面这种写法,不过这种写法需要通过方法访问
  // getters: {
  //   greaterAgeCount: (state) => {
  //     return function (age) {
  //       return state.students.filter((s) => s.age > age).length;
  //     };
  //   },
  // },
....
});

//某组件代码(其他页面访问方式相同)
<template>
  <div>
    <h2>我是某组件代码</h2>
    <h3>这是vuex中的 getter 返回值的数据:{{ stundetConut }}</h3>
    <!-- 也可以采用方法的方式访问,参考如下 -->
    <!-- <h3>
      我是属性写法的得到的getter 返回值 {{ $store.getters.greaterAgeCount(10) }}
    </h3> -->
  </div>
</template>

<script>
export default {
  name: "allHome",
  computed: {
    stundetConut() {
      return this.$store.getters.greaterAgeCount;
    },
  },
  // 也可以采用方法的方式访问,参考如下
  // methods: {
  //   gevue() {
  //     console.log(this.$store.getters.greaterAgeCount(10));
  //   },
  // },
  // mounted() {
  //   this.gevue();
  // },
};
</script>

<style></style>


Mutaion

  1. (只能处理同步)可以理解为官方规定修改Vuexstore 中的状态的唯一方法,类似于事件,如果要修改state的值,需要通过它进行,才能更好的使用 devtools追踪状态变化。当然你也可以不在这里修改,因为这里只是官方推荐的规范和vuex架构设计的概念。
// 代码参考  ... 表示省略这里的代码(因为这里代码是相同的),省略部分请参考对照上方代码

// store.js
....
  mutations: {
    //state 表示数据 value 表示传递过来的值
    addText(state,value) {
      state.text = state.text + value;
    },
  },
....
});

//某组件代码(其他页面访问方式相同)
<template>
  <div>
    <h2>我是某组件代码</h2>
    <h3>这是vuex中的数据:{{ $store.state.text }}</h3>
    <button @click="changeVue('good')">点击变化vuex值</button>
  </div>
</template>

<script>
export default {
  name: "allHome",
  methods: {
    getVuex() {
      console.log("这是vuex中的数据:" + this.$store.state.text);
    },
    changeVue(text) {
      /**
       * 这里通过 this.$store.commit("a",b)可以触发对应事件 a 表示需要vuex中 mutations定义的 事件名,b表示需要传递给vuex的值,如果是多个值请使用对象表示
       * text{
            name:"aaa"
            age:18 
          }
       */
      this.$store.commit("addText", text);
    },
  },
  mounted() {
    this.getVuex();
  },
};
</script>

<style></style>

Action

  1. (可以处理异步),这里可以理解为action是用来分发将要进行的操作事件的,它将要做的事情,提交给mutation,再由mutations进行修改。
  2. 当然这里也可以直接修改state的值(不过在vue2vuex中官方不是很推荐,vue3中的推荐pinia状态管理库已经是使用这个修改State值了)
// store.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
       context.commit('increment')
    }
  }
})

//某组件代码(其他页面访问方式相同)
<template>
  <div>
    <h2>我是某组件代码</h2>
    <div>{{ $store.state.count }}</div>
    <!-- 这里使用 $store.dispatch('a') 触发 mutation 中的事件,再由 mutation 进行修改,其中 a 表示mutation 中的事件名字,  -->
    <button @click="$store.dispatch('increment')">修改count</button>
  </div>
</template>

<script>
export default {
  name: "allHome",
};
</script>

<style></style>

Module

  1. 用于在存在很多状态值的时候,进行模块划分的,特别是在大型应用中,如果使用单一状态树(只写一个文件),应用的所有状态会集中到一个比较大的对象。可能会导致代码不好维护和store 对象臃肿。这时我们可以将 store 分割为模块(module),每个模块拥有自己的 stategettersmutationsactions 、甚至是嵌套子模块——从上至下进行同样方式的分割。
// 主状态store.js

import Vuex from 'vuex';
import moduleA from './modules/modulesA';
import moduleB from './modules/modulesB';

export default new Vuex.Store({
  state: {},
  mutations: {},
  actions: {},
  modules: {
    moduleA
    moduleB,
  },
});

// 拆分的模块状态一 moduleA.js

export default {
  //这里使用的命名空间,避免内部如果存在名字一样方法的时候触发其它模块的同名方法
  //如果使用的命名空间后,如果要触发模块中的的 mutation 就需要加上路径才能调用  store.commit('moduleA/print');
  namespaced: true,
  state: () => ({
    a1: 'aaa',
  }),
  mutations: {
    print(state) {
      console.log(state.a1);
    },
  },
  actions: {},
  getters: {},
};

// 拆分的模块状态一 moduleB.js

export default {
  //这里使用的命名空间,避免内部如果存在名字一样方法的时候触发其它模块的同名方法
  //如果使用的命名空间后,如果要触发模块中的的 mutation 就需要加上路径才能调用  store.commit('moduleA/print');
  namespaced: true,
  state: () => ({
    a2: 'bbb',
  }),
  mutations: {
    print(state) {
      console.log(state.a2);
    },
  },
  actions: {},
  getters: {},
};

vuex辅助函数

  • 主要是为了节约代码书写
  • (备注)辅助函数具体是否使用,可以根据自己需要进行选择

mapState

// 原写法

<template>
  <div>
    <span>{{ customerName }}</span>
  </div>
</template>
<script>
export default {
  computed: {
    customerName() {
      return this.$store.state.customerName;
    }
  }
}
</script>

// 使用 mapState 辅助函数写法
<template>
  <div>
    <span>{{ customerName }}</span>
  </div>
</template>
<script>
import { mapState } from "vuex";
export default {
  computed: mapState({
    // 箭头函数可使代码更简练
    count: (state) => state.count,

    // 传字符串参数 'count' 等同于 `state => state.count`
    countAlias: "count",

    // 为了能够使用 `this` 获取局部状态,必须使用常规函数
    countPlusLocalState(state) {
      return state.count + this.localCount;
    },
  }),
  // 个人推荐这种写法,给 mapState 传一个字符串数组
  
  // 当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组。
  // 映射 this.count 为 store.state.count
  //   computed: mapState([
  //   // 映射 this.count 为 store.state.count
  //   'count'
  // ])
}
</script>

mapGetters

// 原写法
<template>
  <div>
    <span>Shopping Cart ({{ cartItemCount }} items)</span>
  </div>
</template>
<script>
export default {
  computed: {
    cartItemCount() {
      return this.$store.getters.cartItemCount;
    }
  }
}
</script>

// 使用 mapGetters 辅助函数写法

<template>
  <div>
    <span>Shopping Cart ({{ cartItemCount }} items)</span>
  </div>
</template>
<script>
import { mapGetters } from "vuex";
export default {
  computed: {
    ...mapGetters(['cartItemCount'])
  }
}
</script>

mapMutations

// 原写法
<template>
  <div>
    <p>{{ customerName }}</p>
    <input type="text" @input="updateName" :value="customerName" />
  </div>
</template>
<script>
import { mapState } from "vuex";
export default {
  name: "Example",
  computed: {
    ...mapState(['customerName'])
  },
  methods: {
    updateName(event) {
      this.$store.commit('setCustomerName', event.target.value);
    }
  }
}
</script>

// 使用 mapMutations 辅助函数写法

import { mapState, mapMutations } from 'vuex';
export default {
  name: "Example",
  computed: {
    ...mapState(['customerName'])
  },
  methods: {
    ...mapMutations(['setCustomerName']),
    updateName(event) {
      this.setCustomerName(event.target.value);
    }
  }
}

mapActions

// 原写法
<template>
  <div>
    <p>{{ customerName }}</p>
    <input type="text" @input="updateName" :value="customerName" />
  </div>
</template>
<script>
import { mapState } from "vuex";
export default {
  name: "Example",
  computed: {
    ...mapState(['customerName'])
  },
  methods: {
    updateName(event) {
      this.$store.dispatch('setCustomerName', event.target.value);
    }
  }
}
</script>

// 使用 mapActions  辅助函数写法

import { mapState, mapActions  } from 'vuex';
export default {
  name: "Example",
  computed: {
    ...mapState(['customerName'])
  },
  methods: {
    ...mapActions (['setCustomerName']),
    updateName(event) {
      this.setCustomerName(event.target.value);
    }
  }
}

createNamespacedHelpers

  • 如果要使用辅助函数引入vuex模块化中的内容,写起来会比较繁琐
  • 一般解决方法有如下2两种
// 原写法,引入模块化后相对繁琐
// Component.vue

computed: {
  ...mapState({
    a1: (state) => state.moduleA.a1,
    a2: (state) => state.moduleA.a2,
  })
},
methods: {
  ...mapActions({
    foo: 'moduleA/foo',
    boo: 'moduleA/boo',
  }),
}

//解决方法一:可以将模块化的空间名称作为第一个参数传入辅助函数,这样就会自动添加 前缀名 于是上面的例子可以简化为如下写法:

computed: {
  ...mapState('some/nested/module', {
    a: state => state.a,
    b: state => state.b
  })
},
methods: {
  ...mapActions('some/nested/module', [
    'foo', // -> this.foo()
    'bar' // -> this.bar()
  ])
}

// 解决方法二:通过使用 createNamespacedHelpers 创建基于某个命名空间辅助函数。进行自动绑定,使其中能自动查找绑定的模块名

import { createNamespacedHelpers } from 'vuex'

const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')

export default {
  computed: {
    // 在 `some/nested/module` 中查找
    ...mapState({
      a: state => state.a,
      b: state => state.b
    })
  },
  methods: {
    // 在 `some/nested/module` 中查找
    ...mapActions([
      'foo',
      'bar'
    ])
  }
}

在vuex请求数据

  • 之前网上看到有这种写法,本人不是很喜欢这种写法,建议还是按照正常接口方案写

    //vuex 状态文件
    
    import Vue from "vue";
    import Vuex from "vuex";
    import axios from "axios";
    Vue.use(Vuex);
    export default new Vuex.Store({
      state: {
        users: [],
        isLoading: false,
      },
      mutations: {
        setLoadingTrue(state) {
          state.isLoading = true;
        },
        setLoadingFalse(state) {
          state.isLoading = false;
        },
        setUsers(state, users) {
          state.users = users;
        },
        setCustomerName(state, name) {
          state.customerName = name;
        }
      },
      actions: {
        getUsers(context) {
          context.commit('setLoadingTrue');
          axios.get('/api/users')
            .then(response => {
              context.commit('setUsers', response.data);
              context.commit('setLoadingFalse');
            })
            .catch(error => {
              context.commit('setLoadingFalse');
              // handle error
            });
        }
      }
    });
    
    //组件文件
    <template>
      <div>
        <div id="spinner" v-if="isLoading">
          <img src="spinner.gif" />
        </div>
        <ul v-else>
          <li v-for="(user, index) in users" :key="index" >{{ user }}</li>
        </ul>
      </div>
    </template>
    <script>
    import { mapActions, mapState } from "vuex";
    export default {
      computed: {
        ...mapState([
          'isLoading',
          'users'
        ])
      },
      methods: {
        ...mapActions(['getUsers'])
      },
      created() {
        this.getUsers();
      }
    }
    </script>
    

vuex 模块化文件结构参考

├── index.html
├── main.js
├── api
│   └── ... # 抽取出API请求
├── components
│   ├── App.vue
│   └── ...
└── store
    ├── index.js          # 我们组装模块并导出 store 的地方
    ├── actions.js        # 根级别的 action
    ├── mutations.js      # 根级别的 mutation
    └── modules
        ├── cart.js       # 购物车模块
        └── products.js   # 产品模块
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,717评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,501评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,311评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,417评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,500评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,538评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,557评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,310评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,759评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,065评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,233评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,909评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,548评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,172评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,420评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,103评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,098评论 2 352

推荐阅读更多精彩内容

  • 一、企业项目开发流程 产品提需求 交互设计出原型设计 视觉设计出UI设计图 前端开发出页面模板 server端存取...
    Eric_V阅读 1,747评论 0 3
  • ## Vuex Vuex是一个专为Vue.js应用程序开发的**状态管理模式**。 调试工具:vue devtoo...
    flowerxuegao阅读 218评论 0 0
  • 文档:http://vuex.vuejs.org/zh-cn/modules.html这个modules是用来干什...
    杰森999阅读 376评论 0 0
  • Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。 它采用集中式存储管理应用的所有组件的状态...
    月影WEB阅读 392评论 0 1
  • vue中组件之间的通信 组件可以有以下几种关系: [图片上传失败...(image-8918e-166082302...
    流泪手心_521阅读 103评论 0 0