Vuex基础分析

Vuex基本原理和使用

Vuex中核心的三个对象Actions、Mutations和State都由store对象来进行管理,其相互之间的关联通过store.dispatch和store.commit函数来组织,因此配置Vuex的环境的最终目的便是让所有的Vue Components都能够调用store中的各种对象
vuex.png
我们平时说的把数据交给Vuex进行管理,实际上说的就是把数据交给Vuex中的State对象进行管理,State的本质就是一个Object对象,在其中我们统一管理全局共享的数据
Dispatch对应的便是store中的dispatch函数,它的第一个参数表明我们想要做的一个动作名即function_name,第二个参数表明传递的值value,根据function_name去Actions中查找对应的函数
Actions的本质也是一个Object对象,其键是函数名称function_name,值是函数内容function(context,value){},两者可以直接合并为function_name(contexxt,value),注意这里接收的context参数可以看做是全局store 的缩减版(其目的实际上是为当前上下文提供commit函数以及state对象方便其与Mutations进行交互),value参数便是我们在dispatch函数中指定的值,在函数内我们需要去调用commit函数去Mutations中查找对应的函数
Mutations的本质也是一个Object对象,其键是函数名称FUCTION_NAME(出于习惯考虑我们这里的函数名称是Actions中对应的函数名称的大写版本),值是函数内容function(state,value){},两者同样可以合并为FUNCTION(state,value),注意这里接收的参数除了我们在dispatch函数中指定的值外,还有state,即核心对象State的引用对象,此时我们即可对state进行操作,在函数内我们不需要显式调用mutate函数来结束对数据的操作,此后Vue将会基于此时的数据对页面进行重新渲染

下面是上述过程中的几个疑点:

  1. Actions对象的存在意义:对于简单的需求而言,我们可以直接在dispatch函数中指定所要调用的Actions中的函数名称并传递对应的参数,此时在对应的函数体内直接调用commit,在这种情况 下Actions的存在将毫无意义,因此Vue团队允许我们在参数值已知的情况下直接通过commit直接调用Mutations中定义的函数;然而对于相对复杂的需求而言,此时函数所要传递的值是未知的,必须向后台服务器发送请求获取,那么使用Actions进行异步处理便十分有意义。
  2. Devtools直接与Mutations进行交互, 因为Mutations中定义的函数直接操作State对象,因此开发者工具直接监管Mutations

下面是Vuex的基本环境搭建过程:

  1. 安装Vuex:在vue2中需要使用vuex3,在vue3中需要使用vuex4:npm install vuex@3
  2. 在main.js中引入vuex插件(这一步是标准插件的引入手段,但是后续将融入到第四步中)
import Vuex from "vuex";
Vue.use(Vuex);

在这里简单补充一下在main.js中全局引入某插件的直观理解,首先在任何时刻我们都可以为main.js中的new Vue对象的参数传入任意键值对,然而针对这些键值对,某些能够被识别,某些不能够被识别,其关键在于是否引入使用了插件即import和use,当我们引入并使用某个插件后,与插件对应的键值对才能被解析,如引 入vue-router之后router参数将会被解析、引入vuex后store参数将会被解析等等。

  1. 创建store文件,在Vue官方文档中推荐我们创建诸如/src/store/index.js的文件,在该文件中即可定义基于Vuex管理的三个核心对象actions、mutations、state并将它们注册到一个新的Vuex.Store对象中同时导出store
// 引入Vue对象
import Vue from "vue";
// 引入Vuex对象
import Vuex from "vuex";
// 使用Vuex
Vue.use(Vuex);
// 创建Actions对象
const actions = {};
// 创建Mutations对象
const mutations = {};
// 创建State对象
const state = {};
// 导出Store对象
export default new Vuex.Store({
  actions,
  mutations,
  state,
});


此外还有另一个问题需要关注,当我们创建完store对象后并在main.js中引入的时候,我们会发现程序仍然存在问题,这是因为import的优先级比较高,在main.js中会先于其他代码片段优先执行被import的内容,如果我们在main.js中use插件Vuex的话,它的执行顺序会后于对store的import,那么页面便会发生错误,因此需要在/src/store/index.js中use对应的插件,因此在该文件中也需要引入Vue

  1. 在main.js中引入方才创建的store对象
import store from "@/store";
new Vue({
  render: (h) => h(App),
  store,
}).$mount("#app");
  1. 在具体的组件中进行使用,下面用一个登陆登出的案例来模拟基于Vuex的数据管理
<!--模拟登陆登出的效果-->
<template>
  <div>
    <button v-if="$store.state.isLogin" @click="loginController('logout')">
      退出
    </button>
    <button v-if="!$store.state.isLogin" @click="loginController('login')">
      登陆
    </button>
  </div>
</template>

<script>
export default {
  data() {
    return {};
  },
  mounted() {},
  methods: {
    loginController(actionName) {
      this.$store.dispatch("loginController", actionName);
    },
  },
};
</script>

// 引入Vue对象
import Vue from "vue";
// 引入Vuex对象
import Vuex from "vuex";
// 使用Vuex
Vue.use(Vuex);
// 创建Actions对象
const actions = {
  loginController(context, actionName) {
    console.log("当前的行为是", actionName);
    context.commit("LOGIN_CONTROLLER", !context.state.isLogin);
  },
};
// 创建Mutations对象
const mutations = {
  LOGIN_CONTROLLER(state, isLogin) {
    state.isLogin = isLogin;
  },
};
// 创建State对象
const state = {
  isLogin: false,
};
// 导出Store对象
export default new Vuex.Store({
  actions,
  mutations,
  state,
});

在具体的Component对象中使用State内的数据时,如果是使用模板{{}}那么直接 s t o r e . s t a t e 即可,但是如果是在 j s 中调用那么则需要使用 t h i s . store.state即可,但是如果是在js中调用那么则需要使用this. store.state即可,但是如果是在js中调用那么则需要使用this.store.state

尤其需要注意的就是这里的context对象,它的本质是缩减版的store对象而不是Vue对象,因此可以直接调用state属性和commit函数,不需要context.store
一种常见的处理方式是将所有的业务逻辑都在Actions定义的函数中执行,将逻辑处理完毕后选择适合的Mutations函数进行最终的数据处理,直观上和Java后端的MVC模式有点类似,Component属于Views层、Actions处于Controller/Model层,Mutations属于Model层,这种看法仅是个人的拙见不代表大众。
在此基础上我们继续分析Actions函数中context对象的存在意义,对于某些业务逻辑十分复杂的Actions函数,我们有时候需要定义辅助函数便于复用,或者定义对数据的链式操作,此时context对象为我们提供的dispatch函数能够使得在Actions层中继续调用同层的函数,使得复杂的逻辑能够进一步解耦。同时我们不推荐在Actions函数中直接调用State对象进行数据变化,因为在Actions函数中进行的所有数据变化都不会被开发者工具所捕获。
另外,在这种视角上,Component对象中给Actions传递的只需要一些必要的参数即可,例如如果想要获取到一个用户的所有信息包括账号信息、个人信息、订单信息,我们仅需要给Actions传递一个用户的Id即可,具体的业务逻辑不论Actions是在一个函数中完成还是拆分成多个函数完成,并不重要,此后Actions处理完所有的数据后将最终封装好的结果传递给State对象进行数据维护即可。

Vuex开发者工具

首先提供一种简单的方式去为Edge/Chrome浏览器安装Vue插件,不仅是携带有Vue本身所需要的功能,同时能够监测Vuex中数据的变化(需要注意的是开发者工具只能与Mutations中的函数对话)


Vue开发者工具扩展链接,该插件和链接及步骤非本人提供,源于网络,但真实有效
【下载开发者工具】:
提取码6666
【安装开发者工具】:
一、Chrome浏览器安装方式:
①:点击右上角三个点
②:点击更多工具
③:点击扩展程序
④:点击右上角的开发者模式,将他启用
⑤:将下载的Vue.crx文件直接拖动到浏览器窗口即可
二:Edge浏览器安装方式
①:点击浏览器右上角的三个点
②:点击扩展
③:点击左下角的开发人员模式,将他启用
④:将Vue.crx文件拖动到浏览器即可


下面来对上述模拟的登陆登出功能在开发者工具的角度进行分析:
image.png
当我们触发了Mutations中的事件后,选择开发者工具的Vuex模块,便能清晰地看到不同事件的触发顺序和触发后造成的数据变化,数据变化结果在右侧显示,绿色的记录是最后一个执行的Mutations函数,即我们当前数据所处的位置。当我们鼠标移到任何一条记录上的时候,会有三个选项供我们选择,其中Commit即将这一条记录及其之前的记录所造成的变化融合到Base State中,Revert即撤回这一次修改(需要注意的是撤回后从这一条记录开始的所有记录都会被撤回),Time Travel即将时间回溯到发送该记录后的时刻(能够重新回到任意时刻),CommitAll则是合并所有记录到Base State中,Revert All则是撤回所有修改回到原始状态,Recording则表征是否持续进行数据检测,当关闭后将无法接收到Mutations函数的执行结果。
在右侧的数据变化结果中,我们可以导出当前的数据(会自动添加到剪切板中)并在其他任何时候导入以复现当前的操作结果


Getter

经过上述的Vuex基本原理和基本使用后基本上已经具备了使用Vuex的基本能力,但是我们应该想到一种业务场景,在某种情况下不同的组件需要对State对象中某个数据源进行相同的操作,例如将其代入一个现成的计算模型,那么如果在不同组件中都重新计算一遍,一方面计算的开销大,另一方面需要重新写相同的逻辑,这便引入了Getter

当State中的数据需要经过加工后再使用时,可以使用getters加工

  1. 首先我们需要在store的配置文件中引入getters
//创建getters对象
const getters = {
  loginStr(state) {
    return state.isLogin ? "已登陆" : "未登录";
  },
};
// 导出Store对象
export default new Vuex.Store({
  actions,
  mutations,
  state,
  getters,
});

在这里我们相当于对上述State对象中的isLogin进行了数据的二度处理,即根据其值获取当前用户的登陆状态(我们也可以进行更加复杂的处理,这里仅仅是举例说明),在getters中定义的所有函数都默认拥有state对象方便我们进行处理

通俗来说如果把store的配置文件作为一个component的js文件的话,那么State对象就类似data而getters就类似于computed。

  1. 利用getters快速获取数据源的计算结果
<template>
  <div>
    <hi>{{ $store.getters.loginStr }}</hi>
    <button v-if="$store.state.isLogin" @click="loginController('logout')">
      退出
    </button>
    <button v-if="!$store.state.isLogin" @click="loginController('login')">
      登陆
    </button>
  </div>
</template>

mapState和mapGetter

经过上述的优化后,我们能够对某些需要统一计算的State对象所维护的属性通过getters进行维护,但是我们在具体的component组件中进行使用的时候,所有的数据访问都需要通过this. s t o r e 或 store或 storerouter来进行访问,这样在数据量巨大的情况下将显得十分冗余,因此我们引入了mapState和mapGetter来进行数据维护

// 创建State对象
const state = {
  isLogin: false,
  user: {
    name: "Xzh",
    university: "Zjut",
  },
};
//创建getters对象
const getters = {
  loginStr(state) {
    return state.isLogin ? "已登陆" : "未登录";
  },
  userName(state) {
    return state.user.name;
  },
  userUniversity(state) {
    return state.user.university;
  },
};

首先在State对象中增加登陆的用户信息,并在Getters对象中定义用户信息的属性二次计算函数,方便下面的演示

<!--  -->
<template>
  <div>
    <h1>登陆状态:{{ LOGINSTR }}</h1>
    <div v-if="ISLOGIN">
      <h1>用户信息{{ userInfo }}</h1>
      <h2>用户名称{{ USERNAME }}</h2>
      <h2>用户所处学校{{ userUniversity }}</h2>
      <button @click="loginController('logout')">退出</button>
    </div>
    <div v-if="!ISLOGIN">
      <button @click="loginController('login')">登陆</button>
    </div>
  </div>
</template>

<script>
import { mapState, mapGetters } from "vuex";
export default {
  data() {
    return {};
  },
  computed: {
    // 借助mapState生成计算属性,从state中读取数据 对象写法
    ...mapState({ ISLOGIN: "isLogin" }),
    // 借助mapState生成计算属性,从state中读取数据 数组写法
    ...mapState(["userInfo"]),
    // 借助mapGetters生成计算属性,从getters中读取数据 对象写法
    ...mapGetters({ USERNAME: "userName", LOGINSTR: "loginStr" }),
    // 借助mapGetters生成计算属性,从getters中读取数据 数组写法
    ...mapGetters(["userUniversity"]),
  },
  mounted() {},
  methods: {
    loginController(actionName) {
      this.$store.dispatch("loginController", actionName);
    },
  },
};
</script>
<style lang="css" scoped></style>

首先需要介绍一下…在ES6语法中的具体含义,简单来说…就是对某个Object对象的解构,通过这种方式能够把Object对象中的每一组键值对(每一个内容如属性和函数)都展开直接引入到当前位置便于直接访问

mapState和mapGetters都有两种不同的写法即对象写法和数组写法,我们以mapState为例进行讲解。
对象写法使用的应用场景主要是在Component中想要使用的数据名称和在State对象中的不一样,因此需要使用键值对进行映射,如在组件中我们使用的是ISLOGIN来表征State对象中的isLogin属性就需要使用mapState的对象写法,但是如果两者相同如user在组件和State对象中都命名为user那么就可以使用数组写法。
mapState能起到的作用便是省略掉this. s t o r e . s t a t e 这个前缀,对于 m a p G e t t e r s 也是同理。因此基于这两者 m a p 映射,我们就能够让我们的组件中不出现任何 store.state这个前缀,对于mapGetters也是同理。因此基于这两者map映射,我们就能够让我们的组件中不出现任何 store.state这个前缀,对于mapGetters也是同理。因此基于这两者map映射,我们就能够让我们的组件中不出现任何store相关的前缀,简化了大数据规模下的开发。

注意组件数据和State中属性名称一致的时候也不能在对象中采用简写形式即{user} 因为简写形式展开后将会变成key:value,其中value默认为变量而不是字符串,而我们并没有定义同名变量,此时将无法映射得到State中的具体数据

mapActions和mapMutations

既然有mapState和mapGetters那么不难想到的是Vuex也为我们提供了mapActions和mapMutations,这两种map的使用方法与上述两种基本类似。

<!--  -->
<template>
  <div>
    <h1>登陆状态:{{ LOGINSTR }}</h1>
    <div v-if="ISLOGIN">
      <h1>用户信息{{ user }}</h1>
      <h2>用户名称{{ USERNAME }}</h2>
      <h2>用户所处学校{{ userUniversity }}</h2>
      <button @click="loginController('logout')">退出</button>
    </div>
    <div v-if="!ISLOGIN">
      <button @click="LOGIN_CONTROLLER(!ISLOGIN)">登陆</button>
    </div>
  </div>
</template>

<script>
import { mapState, mapGetters, mapActions, mapMutations } from "vuex";
export default {
  data() {
    return {};
  },
  computed: {
    // 借助mapState生成计算属性,从state中读取数据 对象写法
    ...mapState({ ISLOGIN: "isLogin" }),
    // 借助mapState生成计算属性,从state中读取数据 数组写法
    ...mapState(["user"]),
    // 借助mapGetters生成计算属性,从getters中读取数据 对象写法
    ...mapGetters({ USERNAME: "userName", LOGINSTR: "loginStr" }),
    // 借助mapGetters生成计算属性,从getters中读取数据 数组写法
    ...mapGetters(["userUniversity"]),
  },
  mounted() {},
  methods: {
    // 借助mapActions生成对应的方法,方法中会调用dispatch去联系Actions 数组写法
    ...mapActions(["loginController"]),
    // 借助mapMutations生成对应的方法,方法中会调用commit去联系Mutations 对象写法
    ...mapMutations({ LOGIN_CONTROLLER: "LOGIN_CONTROLLER" }),
  },
};
</script>
<style lang="css" scoped></style>

这里分别使用对象写法和数组写法进行演示,并不意味着它们只有这样的写法,而是两者都分别可以使用对象写法和数组写法,规则和mapState相同。

有一个需要特别注意的地方是此时map帮我们自动生成了对应的函数,但是并没有帮我们自动生成参数,因此我们在调用的过程需要手动传递参数,否则将会把event对象传给对应的Actions和Mutations函数

Vuex的模块化和namespace

当我们具备上述的各种使用方法后基本就能够以相对简单的方式进行开发了,然而有时候页面的业务逻辑相对复杂化,具有许多不同的模块,这时候如果把所有模块的数据和函数都写到同一个States、Actions和Mutations中将会造成结构混乱,耦合度高的情况,这时候就需要使用Vuex提供的模块化编程了。
例如,我们将上述程序抽象成不同的模块的话,可以简单分成登陆模块和用户信息模块,此时我们需要将它们分别进行配置,即将原先的各函数内的内容分散到各模块对应的部分,下面是重构后的store配置文件

// 引入Vue对象
import Vue from "vue";
// 引入Vuex对象
import Vuex from "vuex";
// 使用Vuex
Vue.use(Vuex);


const loginOptions = {
  namespaced: true,
  actions: {
    loginController(context, actionName) {
      console.log("当前的行为是", actionName);
      context.commit("LOGIN_CONTROLLER", !context.state.isLogin);
    },
  },
  mutations: {
    LOGIN_CONTROLLER(state, isLogin) {
      state.isLogin = isLogin;
    },
  },
  state: {
    isLogin: false,
  },
  getters: {
    loginStr(state) {
      return state.isLogin ? "已登陆" : "未登录";
    },
  },
};
const userOptions = {
  namespaced: true,
  actions: {},
  mutations: {},
  state: {
    user: {
      name: "Xzh",
      university: "Zjut",
    },
  },
  getters: {
    userName(state) {
      return state.user.name;
    },
    userUniversity(state) {
      return state.user.university;
    },
  },
};

// 导出Store对象
export default new Vuex.Store({
  modules: {
    userAbout: userOptions,
    loginAbout: loginOptions,
  },
});

下面是利用模块化编程改进后的登陆组件

<!--  -->
<template>
  <div>
    <h1>登陆状态:{{ LOGINSTR }}</h1>
    <div v-if="ISLOGIN">
      <h1>用户信息{{ userAbout.user }}</h1>
      <h2>用户名称{{ USERNAME }}</h2>
      <h2>用户所处学校{{ userUniversity }}</h2>
      <button @click="loginController('logout')">退出</button>
    </div>
    <div v-if="!ISLOGIN">
      <button @click="LOGIN_CONTROLLER(!ISLOGIN)">登陆</button>
    </div>
  </div>
</template>

<script>
import { mapState, mapGetters, mapActions, mapMutations } from "vuex";
export default {
  data() {
    return {};
  },
  computed: {
    // 借助mapState生成计算属性,从state中读取数据 对象写法
    ...mapState("loginAbout", { ISLOGIN: "isLogin" }),
    // 借助mapState生成计算属性,从state中读取数据 数组写法
    ...mapState("userAbout", ["user"]),
    // 借助mapGetters生成计算属性,从getters中读取数据 对象写法
    ...mapGetters("loginAbout", { USERNAME: "userName", LOGINSTR: "loginStr" }),
    ...mapGetters("userAbout", { LOGINSTR: "loginStr" }),
    // 借助mapGetters生成计算属性,从getters中读取数据 数组写法
    ...mapGetters("userAbout", ["userUniversity"]),

    ...mapState(["loginAbout", "userAbout"]),
  },
  mounted() {},
  methods: {
    ...mapActions("loginAbout", ["loginController"]),
    ...mapMutations("loginAbout", { LOGIN_CONTROLLER: "LOGIN_CONTROLLER" }),
  },
};
</script>
<style lang="css" scoped></style>

这里需要注意的是在使用map进行属性以及函数导入的时候需要指定导入的是哪个模块的内容,不能像之前那样直接使用对象和数组方式直接引入,而是需要指定具体的模块名称,这里的模块名称即我们在store配置文件中指定的models参数下的键的名称(需要强调的是,这种方式只有在对应的模块下namespace参数指定为true的时候才能生效,否则只能用数组或对象方式引入模块名称,然后再指定属性,例如这里的userAbout.user的使用)
开启命名空间后读取State和Getters
其次就是我们配置的虽然是loginOptions和userOptions下面包含有Actions、Mutations、State和Getters对象且模块化时也是使用loginAbout和userAbout但是并不意味着我们直接访问时使用的是this.$store.loginAbout.state.isLogin反而是this.$store.state.loginAbout.isLogin即Vuex在内部将这两个模块的不同对象分别封装到了对应的部分,这样才能够配合map进行解构;但是我们并不能将getters和state的使用方法等同,如果想要直接访问getters中的数据,那么需要使用this.$store.getters['loginAbout/loginStr']这里的getters是一个全局函数,因此会将对应的对应的内容封装到其中而不是分类展示,不使用.来访问数据是因为在.后面不能出现/所以必须使用方括号来访问属性。
开启命名空间后读取Actions和Mutations
另外上述说明了模块化访问State数据时的规则,现在说明一下模块化情况下不依赖于map访问Acntions和Mutations的规则,即使用this.$store.dispatch('loginAbout/loginController',value)要特别注意并不是this.$store.dispatch.loginAbout('loginController',value)或者this.$store.dispatch('loginAbout','loginController',value)
命名空间内部独立且隔离
此外,对于某一个模块内部的state那么就可以直接使用state进行数据读取,例如在loginOptions中的getters中定义的loginStr函数就可以直接通过state来访问数据而不是使用state.loginAbout.state或者state.loginOptions.state,对于Actions和Mutations同理

这样就能够总结如果不依赖于map那么其实都是需要通过对应的对象然后再访问具体的数据的,即先xxxAbout然后actions/mutations中的函数,getters其实本质相同只不过使用/来作为分隔符了,但是state比较特殊,他是先state然后再xxxAbout

最后的总结就是能够使用map就是用map进行映射。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值