Vuex快速掌握


Vuex 快速掌握

什么是 Vuex?

首先我们要弄清楚 Vuex 是做什么的?为什么使用 Vuex ?

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。uniapp 集成了 vuex 的状态管理功能,可以在多端情况下使用。

Vuex 需要解决的问题:

  1. 多个视图依赖于同一状态。比如当前音乐应用的账号页,我的页面保持登录状态以及用户信息。
  2. 来自不同视图的行为需要变更同一状态。比如登录页更改登录状态,账号页面退出登录改成未登录状态。

这个状态自管理应用包含以下几个部分:

  • state,驱动应用的数据源;
  • view,以声明方式将 state 映射到视图;
  • actions,响应在 view 上的用户输入导致的状态变化。

在这引用了一位技术大拿关于管理状态(state)的讲解:

不管是 Vue ,还是 React,都需要管理状态(state),比如组件之间都有共享状态的需要。什么是共享状态?比如一个组件需要使用另一个组件的状态,或者一个组件需要改变另一个组件的状态,都是共享状态。

父子组件之间,兄弟组件之间共享状态,往往需要写很多没有必要的代码,比如把状态提升到父组件里,或者给兄弟组件写一个父组件,听听就觉得挺啰嗦。

如果不对状态进行有效的管理,状态在什么时候,由于什么原因,如何变化就会不受控制,就很难跟踪和测试了。如果没有经历过这方面的困扰,可以简单理解为会搞得很乱就对了。

在软件开发里,有些通用的思想,比如隔离变化,约定优于配置等,隔离变化就是说做好抽象,把一些容易变化的地方找到共性,隔离出来,不要去影响其他的代码。约定优于配置就是很多东西我们不一定要写一大堆的配置,比如我们几个人约定,view 文件夹里只能放视图,不能放过滤器,过滤器必须放到 filter 文件夹里,那这就是一种约定,约定好之后,我们就不用写一大堆配置文件了,我们要找所有的视图,直接从 view 文件夹里找就行。

根据这些思想,对于状态管理的解决思路就是:把组件之间需要共享的状态抽取出来,遵循特定的约定,统一来管理,让状态的变化可以预测。根据这个思路,产生了很多的模式和库。

Vuex 防止随意修改而不好跟踪状态,规定组件不允许直接修改 store 实例的 state,组件必须通过 action 来改变 state ,也就是说,组件里面应该执行 action 来分发 (dispatch) 事件通知 store 去改变。这样约定的好处是,我们能够记录所有 store 中发生的 state 改变,同时实现能做到记录变更 (mutation)、保存状态快照、历史回滚的先进的调试工具。

Vuex 的基础使用(创建一个改变登录状态的应用)

我们一个简单的登录状态的应用开始。

第一步,创建 store

首先在根目录下新建文件夹 store,并创建 index.js :

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

const store = new Vuex.Store({
  state: {
    isLogin: false, // 是否登录的状态,默认为未登录 false
  },
  mutations: {
    // 定义一个操作isLogin状态的方法
    storeLogin(state) {
      state.isLogin = true;
    },
  },
});

export default store;

上面代码引入了 vuevuex ,并使用 Vue.use(Vuex) 安装 Vuex 插件,在 new Vuex.Store 传参对象中定义 statemutations 。定义了 isLogin,整个项目以这个变量作为登录标记,storeLogin 的方法来修改 isLogin 值,而且修改 isLogin 值只能通过 storeLogin 方法。

第二步,新建登录页 login.vue

第三步,引入 Vuex

在主入口 main.js 引入刚才新建的 store:

import Vue from "vue";
import App from "./App";
import store from "./store";

Vue.prototype.$store = store;
Vue.config.productionTip = false;

App.mpType = "app";

const app = new Vue({
  ...App,
});
app.$mount();

使用 Vue.prototype.$store = storestore 挂在到 Vue 中,这样整个项目就可以共享这个 $store 状态,通过在根实例中注册 store 选项,该 store 实例会注入到根组件下的所有子组件中,且子组件能通过 this.$store 访问到。下面要在 login.vue 页面共享 isLogin 登录状态;

<!-- login.vue -->
<template>
  <view> 登录状态: {{ $store.state.isLogin }} </view>
</template>

<script>
export default {
  data() {
    return {};
  },
};
</script>

<style lang="scss"></style>

第四步:访问 Vuex 定义变量

在应用启动情况下访问 http://localhost:8080/#/pages/login/login 可以看到 login.vue 页面上的 {{$store.state.isLogin}} 被渲染成了一个 false,这个 false 是第一步在文件 store/index.js 中添加的 isLogin 变量。

因为有 Vue.prototype.$store = store 这样我们就可以在页面组件中以 $store 访问 state 定义下的所有状态变量,也就是说你可以在页面 B,页面 C,甚至是页面 Y 都可以访问到这个变量。

那如果我定义的变量很多或者很长呢,这种写法 $store.state.isLogin 有没有更加便捷的方法呢?

Vuex 的 state 多种用法

Vuex 封装了一些辅助函数 mapState 方法,让你写的状态可以映射出来,减少查询。当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性,让你少按几次键。这样我们可以简化一下 login.vue 代码:

<!-- login.vue -->
<template>
  <view> 登录状态: {{ isLogin }} 登录状态: {{ hasLogin }} </view>
</template>

<script>
import { mapState } from "vuex";
export default {
  data() {
    return {};
  },
  computed: mapState({
    // 箭头函数可使代码更简练
    isLogin: (state) => state.isLogin,
    // 传字符串参数 'isLogin' 等同于 `state => state.isLogin`
    hasLogin: "isLogin",
  }),
  // computed: {
  //    ...mapState(['isLogin'])
  // },
};
</script>
<style lang="scss"></style>

script 中引入辅助函数 import { mapState } from 'vuex',就可以在 computed 对象中使用 mapState 辅助函数了;

上面第一个写法中 由 state.isLogin 映射到状态 isLogin,第二个写法是直接以传字符串参数的形式将 Vuex 的储存状态 isLogin 直接映射到 hasLogin ,保存后可以在浏览器看到 isLoginhasLogin 渲染是一样的。

好像还不够简便的样子,那来一个更简便的写法:

computed: {
   ...mapState(['isLogin'])
},

这样的形式也可以访问 isLogin 状态,该写法运用了 es6 中的 ... 对象扩展运算符号,意思是里面的数组值 ['isLogin'] 通过 mapState 辅助方法映射出来之后,再通过扩展运算符一个一个对应出来,这样就可以在视图直接访问了 {{isLogin}},如果有多个状态值(比如还有 ‘stateA’ , ‘stateB’ )就显得便捷很多了,不用写多余的方法:

computed: {
   ...mapState(['isLogin', 'stateA', 'stateB'])
},

上面只是介绍如何获取 state 状态,那如果想要改变状态呢?

Vuex 的 Mutation 用法

还记得我们在 store/index.js 文件中定义一个操作 isLogin 状态的方法吗?

// ...
mutations: {
    // 定义一个操作isLogin状态的方法
    storeLogin (state) {
      state.isLogin = true
    }
}

更改 Vuex 的 store 中的状态的唯一方法是提交 mutation (在 mutations 中定义的方法),我们可以访问事件去触发 storeLogin() 更改登录状态,重新回到 login.vue 页面,添加一个按钮方法去触发 storeLogin():

<!-- login.vue -->
<template>
  <view>
    <view> 登录状态: {{ isLogin }} </view>
    <button @click="login">登录</button>
  </view>
</template>

<script>
import { mapState } from "vuex";
export default {
  data() {
    return {};
  },
  computed: {
    ...mapState(["isLogin"]),
  },
  methods: {
    // 登录
    login() {
      this.$store.commit("storeLogin");
    },
  },
};
</script>
<style lang="scss"></style>

当我们点击登录按钮的时候执行 this.$store.commit('storeLogin') 就可以把登录状态修改为 true 了。

我们不能直接调用一个 mutation 方法事件,要调用 store.commit 方法去触发,相当于中间搭了一个桥来衔接这些方法。

在程序设定开发中我们肯定会改变很多状态,不仅仅是把未登录改为登录,还会退出登录改为未登录,这样我们可以不用写一个退出登录的方法,我们直接传递一个参数过去就可以搞定了,回到 store/index.js 文件中 storeLogin() 这个方法会接受 state 作为第一个参数,自定义参数作为余后的参数,通常把这叫做 载荷payload

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

const store = ``({
  state: {
    isLogin: false, // 是否登录的状态,默认为未登录 false
  },
  mutations: {
    // 定义一个操作isLogin状态的方法
    storeLogin(state, payload) {
      state.isLogin = true;
    },
  },
});

export default store;

这样我们在 login.vue 页中就可以传参数了。

<!-- login.vue -->
<template>
  <view>
    <view> 登录状态: {{ isLogin }} </view>
    <button @click="login">登录</button>
    <button @click="logout">退出</button>
  </view>
</template>

<script>
import { mapState } from "vuex";
export default {
  data() {
    return {};
  },
  computed: {
    ...mapState(["isLogin"]),
  },
  methods: {
    // 登录
    login() {
      this.$store.commit("storeLogin", true);
    },
    // 退出
    login() {
      this.$store.commit("storeLogin", false);
    },
  },
};
</script>
<style lang="scss"></style>

我们再添加一个退出按钮,同样调用触发 storeLogin() 修改登录状态,这样只是更改参数就可以改变登录状态了,是不是很简单 ,再优化一下代码:

<!-- login.vue -->
<template>
  <view>
    <view> 登录状态: {{ isLogin }} </view>
    <button @click="login(true)">登录</button>
    <button @click="login(false)">退出登录</button>

    <navigator url="../index/index">去首页</navigator>
  </view>
</template>

<script>
import { mapState } from "vuex";
export default {
  data() {
    return {};
  },
  computed: {
    ...mapState(["isLogin"]),
  },
  methods: {
    // 改变登录状态
    login(bool) {
      this.$store.commit("storeLogin", bool);
    },
  },
};
</script>
<style lang="scss"></style>

修改首页,让首页也可以访问登录状态 isLogin

<!-- 首页 index.vue -->
<template>
  <view class="content">
    <view class="text-area">
      <text class="title">当前是首页</text>
      <view> 登录状态: {{ isLogin }} </view>
    </view>
  </view>
</template>

<script>
import { mapState } from "vuex";
export default {
  data() {
    return {};
  },
  computed: {
    ...mapState(["isLogin"]),
  },
  methods: {},
};
</script>
<style></style>

在浏览器运行,你就可以尝试改变登录状态的时候去首页查看,发现首页也是登录的。如果实现了,恭喜你已经掌握了 Vuex 。

Vuex 还有 Action 概念,可以包含任意异步操作,如果你使用了异步操作,直接调用 mutation 里面的方法可能并不会成功,因为 mutation 必须同步执行。

如果你的应用模块足够多的话,可以以模块的方式管理这些,比如客户模块,商品模块,这样这些状态就可以轻松管理了。整个项目,无论是页面还是组件都可以用上面提到的方式访问到 state 和修改 state。

然后再回顾一下这篇文章的第一个图,你就能轻松了解 Vuex 的机制了。

想要查看更多的理论知识可以查看 官网 vuex

小结

  1. Vuex 是一个专为应用程序开发的状态管理模式。
  2. 更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。我们不能直接调用一个 mutation 方法事件,我们要调用 store.commit 方法去触发,相当于中间搭了一个桥来衔接这些方法。
  3. 合理的使用 Vuex 可以让我们友好便捷的管理状态,不仅是登录状态,可以是用户信息,可以是一个修改标记,但使用 Vuex 可能是繁琐冗余的。——如果你的应用够简单,最好不要使用 Vuex。

文章作者: Syhan
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Syhan !
评论
  目录