Vuex 快速掌握
什么是 Vuex?
首先我们要弄清楚 Vuex 是做什么的?为什么使用 Vuex ?
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。uniapp 集成了 vuex 的状态管理功能,可以在多端情况下使用。
Vuex 需要解决的问题:
- 多个视图依赖于同一状态。比如当前音乐应用的账号页,我的页面保持登录状态以及用户信息。
- 来自不同视图的行为需要变更同一状态。比如登录页更改登录状态,账号页面退出登录改成未登录状态。
这个状态自管理应用包含以下几个部分:
- 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;
上面代码引入了 vue
,vuex
,并使用 Vue.use(Vuex)
安装 Vuex 插件,在 new Vuex.Store
传参对象中定义 state
,mutations
。定义了 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 = store
把 store
挂在到 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
,保存后可以在浏览器看到 isLogin
,hasLogin
渲染是一样的。
好像还不够简便的样子,那来一个更简便的写法:
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。
小结
- Vuex 是一个专为应用程序开发的状态管理模式。
- 更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。我们不能直接调用一个 mutation 方法事件,我们要调用 store.commit 方法去触发,相当于中间搭了一个桥来衔接这些方法。
- 合理的使用 Vuex 可以让我们友好便捷的管理状态,不仅是登录状态,可以是用户信息,可以是一个修改标记,但使用 Vuex 可能是繁琐冗余的。——如果你的应用够简单,最好不要使用 Vuex。