Vuex是什么?
官方解释:
Vuex
是一个专为
Vue.js
应用程序开发的
状态管理模式
。
它采用
集中式存储管理
应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
状态管理
到底是什么?
状态管理模式、集中式存储管理
这些名词听起来就非常高大上,让人捉摸不透。
其实,你可以简单的将其看成把需要多个组件共享的变量全部存储在一个对象里面。
然后,将这个对象放在顶层的
Vue
实例中,让其他组件可以使用。
那么,多个组件是不是就可以共享这个对象中的所有变量属性了呢?
等等,如果是这样的话,为什么官方还要专门出一个插件
Vuex
呢?难道我们不能自己封装一个对象来管理吗?
当然可以,只是我们要先想想
VueJS
带给我们最大的便利是什么呢?没错,就是响应式。
如果你自己封装实现一个对象能不能保证它里面所有的属性做到响应式呢?当然也可以,只是自己封装可能稍微麻烦一些。
不用怀疑,
Vuex
就是为了提供这样一个在多个组件间共享状态的插件,用它就可以了。
管理什么状态呢?
但是,有什么状态时需要我们在多个组件间共享的呢?
如果你做过大型开放,你一定遇到过多个状态,在多个界面间的共享问题。
比如用户的登录状态、用户名称、头像、地理位置信息等等。
比如商品的收藏、购物车中的物品等等。
这些状态信息,我们都可以放在统一的地方,对它进行保存和管理,而且它们还是响应式的(待会儿我们就可以看到代码了,莫着急)。
OK
,从理论上理解了状态管理之后,让我们从实际的代码再来看看状态管理。
毕竟,
Talk is cheap, Show me the code.(
来自
Linus)
单界面的状态管理
我们知道,要在单个组件中进行状态管理是一件非常简单的事情
什么意思呢?我们来看下面的图片。
这图片中的三种东西,怎么理解呢?
State
:不用多说,就是我们的状态。(你姑且可以当做就是
data
中的属性)
View
:视图层,可以针对
State
的变化,显示不同的信息。(这个好理解吧?)
Actions
:这里的
Actions
主要是用户的各种操作:点击、输入等等,会导致状态的改变。
写点代码,加深理解:
看下右边的代码效果
,
肯定会实现吧
?
1.安装vuex插件
npm install vuex --save
2.创建store文件夹添加index.js
import Vue from 'vue'
import Vuex from 'vuex'
//1.安装插件
Vue.use(Vuex)
//2.创建对象
const store = new Vuex.Store({
state: {
counter: 1000
},
mutations: {},
actions: {},
getters: {},
modules: {}
})
//3.导出store对象
export default store;
3.在main.js中引入
4.使用$store.state.变量名
多界面状态管理
Vue
已经帮我们做好了单个界面的状态管理,但是如果是多个界面呢?
多个试图都依赖同一个状态(一个状态改了,多个界面需要进行更新)
不同界面的
Actions
都想修改同一个状态(
Home.vue
需要修改,
Profile.vue
也需要修改这个状态)
也就是说对于某些状态
(
状态
1/
状态
2/
状态
3)
来说只属于我们某一个试图,但是也有一些状态
(
状态
a/
状态
b/
状态
c)
属于多个试图共同想要维护的
状态
1/
状态
2/
状态
3
你放在自己的房间中,你自己管理自己用,没问题。
但是状态
a/
状态
b/
状态
c
我们希望交给一个大管家来统一帮助我们管理!!!
没错,
Vuex
就是为我们提供这个大管家的工具。
全局单例模式(大管家)
我们现在要做的就是将共享的状态抽取出来,交给我们的大管家,统一进行管理。
之后,你们每个试图,按照我
规定好的
规定,进行访问和修改等操作。
这就是
Vuex
背后的基本思想。
注意:Actions: 做异步操作 Mutations: 做同步操作
首先,我们需要在某个地方存放我们的Vuex代码:
这里,我们先创建一个文件夹store,并且在其中创建一个index.js文件
在index.js文件中写入如下代码:
使用
Vuex核心概念
Vuex有几个比较核心的概念:
- State
- Getters
- Mutation
- Action
- Module
State单一状态树
Vuex
提出使用单一状态树
,
什么是单一状态树呢?
英文名称是
Single Source of Truth
,也可以翻译成单一数据源。
但是,它是什么呢?我们来看一个生活中的例子。
OK
,我用一个生活中的例子做一个简单的类比。
我们知道,在国内我们有很多的信息需要被记录,比如上学时的个人档案,工作后的社保记录,公积金记录,结婚后的婚姻信息,以及其他相关的户口、医疗、文凭、房产记录等等(还有很多信息)。
这些信息被分散在很多地方进行管理,有一天你需要办某个业务时
(
比如入户某个城市
)
,你会发现你需要到各个对应的工作地点去打印、盖章各种资料信息,最后到一个地方提交证明你的信息无误。
这种保存信息的方案,不仅仅低效,而且不方便管理,以及日后的维护也是一个庞大的工作
(
需要大量的各个部门的人力来维护,当然国家目前已经在完善我们的这个系统了
)
。
这个和我们在应用开发中比较类似:
如果你的状态信息是保存到多个
Store
对象中的,那么之后的管理和维护等等都会变得特别困难。
所以
Vuex
也使用了单一状态树来管理应用层级的全部状态。
单一状态树能够让我们最直接的方式找到某个状态的片段,而且在之后的维护和调试过程中,也可以非常方便的管理和维护。
Getters基本使用
Mutation状态更新
Vuex
的
store
状态的更新唯一方式:
提交
Mutation
Mutation
主要包括两部分:
字符串的
事件类型(
type
)
一个
回调函数(
handler
)
,
该回调函数的第一个参数就是
state
。
Mutation传递参数
Mutation提交风格
Mutation响应规则
Vuex
的
store
中的
state
是响应式的
,
当
state
中的数据发生改变时
, Vue
组件会自动更新
.
这就要求我们必须遵守一些
Vuex
对应的规则
:
提前在store中初始化好所需的属性.
当给state中的对象添加新属性时, 使用下面的方式:
- 方式一: 使用Vue.set(obj, 'newProp', 123)
- 方式二: 用心对象给旧对象重新赋值
我们来看一个例子
:
当我们点击更新信息时
,
界面并没有发生对应改变
.
如何才能让它改变呢
?
查看下面代码的方式一和方式二
都可以让
state
中的属性是响应式的
.
Mutation常量类型 – 概念
我们来考虑下面的问题
:
在
mutation
中
,
我们定义了很多事件类型
(
也就是其中的方法名称
).
当我们的项目增大时
,
Vuex
管理的状态越来越多
,
需要更新状态的情况越来越多
,
那么意味着
Mutation
中的方法越来越多
.
方法过多
,
使用者需要花费大量的经历去记住这些方法
,
甚至是多个文件间来回切换
,
查看方法名称
,
甚至如果不是复制的时候
,
可能还会出现写错的情况
.
如何避免上述的问题呢
?
在各种
Flux
实现中
,
一种很常见的方案就是使用
常量
替代
Mutation
事件的类型
.
我们可以将这些常量放在一个单独的文件中
,
方便管理以及让整个
app
所有的事件类型一目了然
.
具体怎么做呢
?
我们可以创建一个文件
: mutation-types.js,
并且在其中定义我们的常量
.
定义常量时
,
我们可以使用
ES2015
中的风格
,
使用一个常量来作为函数的名称
.

Mutation同步函数
通常情况下
,
Vuex
要求我们
Mutation
中的方法必须是同步方法
.
主要的原因是当我们使用
devtools
时
,
可以
devtools
可以帮助我们捕捉
mutation
的快照
.
但是如果是异步操作
,
那么
devtools
将不能很好的追踪这个操作什么时候会被完成
.
Action的基本定义
我们强调
,
不要再
Mutation
中进行异步操作
.
但是某些情况
,
我们确实希望在
Vuex
中进行一些异步操作
,
比如网络请求
,
必然是异步的
.
这个时候怎么处理呢
?
Action
类似于
Mutation,
但是是用来代替
Mutation
进行异步操作的
.
Action
的基本使用代码如下
:
context
是什么
?
context
是和
store
对象具有相同方法和属性的对象
.
也就是说
,
我们可以通过
context
去进行
commit
相关的操作
,
也可以获取
context.state
等
.
但是注意
,
这里它们并不是同一个对象
,
为什么呢
?
我们后面学习
Modules
的时候
,
再具体说
.
这样的代码是否多此一举呢
?
我们定义了
actions,
然后又在
actions
中去进行
commit,
这不是脱裤放屁吗
?
事实上并不是这样
,
如果在
Vuex
中有异步操作
,
那么我们就可以在
actions
中完成了
.
Action的分发
Action返回的Promise
Module
import Vue from 'vue'
import Vuex from 'vuex'
import { INCREMENT } from "./mutations-types";
//1.安装插件
Vue.use(Vuex)
const moduleA = {
state: {
name: 'zhangsan'
},
mutations: {
updateName(state, payload){
state.name = payload
}
},
actions: {
aUpdateName(context){
console.log(context.rootGetters)
setTimeout(
()=>{
context.commit('updateName', 'wangwu')
},1000)
}
},
getters: {
fullName(state){
return state.name + "1111";
},
fullName2(state,getters){
return getters.fullName + '222';
},
fullName3(state,getters,rootCounter){
return getters.fullName2 + rootCounter.counter;
}
}
}
//2.创建对象
const store = new Vuex.Store({
state: {
counter: 1000,
students: [
{id: 110, name: 'why0', age: 18},
{id: 111, name: 'why1', age: 19},
{id: 112, name: 'why2', age: 20},
{id: 113, name: 'why3', age: 21},
],
info: {
name: 'kobe',
age: 40,
height: 1.98
}
},
mutations: {
//方法
[INCREMENT](state) {
state.counter++
},
decrement(state) {
state.counter--
},
incrementCount(state,payload){
//1.普通的提交封装
// state.counter += count;
//2.特殊的提交封装
state.counter += payload.count;
},
addStudent(state,stu){
state.students.push(stu);
},
updateInfo(state){
// state.info.name ='panghl';
// state.info['address'] ='成都市'; 不是响应式的
//Vue.set(state.info,'address','成都市')
// delete state.info.age 该方法做不到响应式
Vue.delete(state.info, 'age')
}
},
actions: {
//context: 上下文
// aUpdateInfo(context,payload){
// setTimeout(()=>{
// context.commit('updateInfo')
// console.log(payload.message);
// payload.success();
// },1000)
// }
aUpdateInfo(context,payload){
return new Promise((resolve, reject) => {
setTimeout(()=>{
context.commit('updateInfo')
console.log(payload);
resolve(payload)
},1000)
})
}
},
getters: {
powerCounter(state){
return state.counter * state.counter;
},
more20stu(state){
return state.students.filter(s=>s.age>=20)
},
more20stuLength(state,getters){
return getters.more20stu.length;
},
//getters默认是不能传递参数的, 如果希望传递参数, 那么只能让getters本身返回另一个函数
moreAgeStu(state){
return function (age) {
return state.students.filter(s=>s.age>=age)
}
}
},
modules: {
a: moduleA
}
})
//3.导出store对象
export default store;
<template>
<div id="app">
<h2>-----------APP内容:modules中的内容-------------</h2>
<h2>{
{$store.state.a.name}}</h2>
<button @click="updateName">修改名字</button>
<button @click="asyncUpdateName">异步修改名字</button>
<h2>{
{$store.getters.fullName}}</h2>
<h2>{
{$store.getters.fullName2}}</h2>
<h2>{
{$store.getters.fullName3}}</h2>
<h2>-----------APP内容-------------</h2>
<h2>{
{message}}</h2>
<h2>{
{$store.state.counter}}</h2>
<button @click="addition">+</button>
<button @click="subtraction">-</button>
<button @click="addCount(5)">+5</button>
<button @click="addCount(10)">+10</button>
<button @click="addStudent">+student</button>
<button @click="updateInfo">updateInfo</button>
<h2>-----------APP内容:getters相关信息-------------</h2>
<h2>{
{$store.getters.powerCounter}}</h2>
<li>{
{$store.getters.more20stu}}</li>
<li>{
{$store.getters.more20stuLength}}</li>
<li>{
{$store.getters.moreAgeStu(8)}}</li>
<h2>---------------APP内容: info对象的内容是否是响应式的----------</h2>
<h2>{
{$store.state.info}}</h2>
<h2>---------------Hello Vuex内容----------</h2>
<hello-vuex></hello-vuex>
</div>
</template>
<script>
import HelloVuex from "./components/HelloVuex";
import {INCREMENT} from './store/mutations-types'
export default {
name: 'App',
components: {HelloVuex},
data(){
return {
message: '你好'
}
},
computed: {
},
methods: {
addition(){
this.$store.commit(INCREMENT)
},
subtraction(){
this.$store.commit('decrement')
},
addCount(count){
//payload: 负载
//1.普通的提交封装
//this.$store.commit('incrementCount',count);
//2.特殊的提交封装
this.$store.commit({
type: 'incrementCount',
count
})
},
addStudent(){
const stu = {id: 14,name: 'panghl' , age:22}
this.$store.commit('addStudent',stu);
},
updateInfo(){
// this.$store.commit('updateInfo');
// this.$store.dispatch("aUpdateInfo",{
// message: '我是携带的信息',
// success: ()=>{
// console.log('里面已经完成了');
// }
//})
this.$store
.dispatch('aUpdateInfo', '我是携带的信息')
.then(res=>{
console.log('里面完成了提交');
console.log(res+"then....");
})
},
updateName() {
this.$store.commit('updateName','lisi')
},
asyncUpdateName(){
this.$store.dispatch('aUpdateName')
}
}
}
</script>
<style>
</style>
项目结构
当我们的
Vuex
帮助我们管理过多的内容时
,
好的项目结构可以让我们的代码更加清晰
.