声明:本章内容属于本人发布到GitChat
平台中《深入浅出学习Vue开发》中的部分内容,在争取了发布平台的同意之后,才发布于慕课网。
这一章我们来学习Vuex,如果要学习好Vuex那么最最重要的就是要知道Vuex
是干嘛的,我们为什么需要Vuex
。因为对于Vuex
来说,它的使用方式非常简单,知识点也不多。Vuex
唯一的难点就是很多人无法理解它。所以在本章我们会着重的讲解Vuex
的作用,我们为什么需要它,在理解了这些之后,我们再去学习Vuex
的使用就会水到渠成了。
状态管理
我们直接来看这一段代码:
<div id="app">
<com-1></com-1>
<com-2></com-2>
</div>
<script type="text/x-template" id="com-1">
<div>
<input type="button" @click="addCount" value="count++">
</div>
</script>
<script type="text/x-template" id="com-2">
<div>
count: {{count}}
</div>
</script>
<script>
Vue.component('com-1', {
template: '#com-1',
data: function () {
return {
count: 0
}
},
methods: {
addCount: function () {
this.count += 1;
}
}
});
Vue.component('com-2', {
template: '#com-2',
data: function () {
return {
count: 0
}
},
});
var vm = new Vue({
el: '#app',
});
</script>
在这段代码中,我们期望能够通过点击com-1
中的count++
按钮来改变com-2
中的count
,使其每点击一下自加一。基于这个需求我们应该如何去实现呢?基于我们现在所学到的知识,一共有两种解决办法,我们来看一下。
第一种解决方案就是:通过组件传参的方式。我们知道在Vue
中,兄弟组件是没有办法进行传参操作的,那么按照我们现在所学到的知识,如果希望通过组件间传参的方式解决这个问题,那么就需要对我们的html
结构进行一下修改,我们看下面的代码:
<div id="app">
<com-1></com-1>
</div>
<script type="text/x-template" id="com-1">
<div>
<input type="button" @click="addCount" value="count++">
<com-2 v-bind:count="count"></com-2>
</div>
</script>
<script type="text/x-template" id="com-2">
<div>
count: {{count}}
</div>
</script>
<script>
Vue.component('com-1', {
template: '#com-1',
data: function () {
return {
count: 0
}
},
methods: {
addCount: function () {
this.count += 1;
}
}
});
Vue.component('com-2', {
template: '#com-2',
props: {
count: 0
}
});
var vm = new Vue({
el: '#app'
});
</script>
在上面的代码中我们把com-2
变成了com-1
的一个子组件,当我们点击count++
按钮的时候,我们通过props
向com-2
中传递了count
的值,我们在学习组件的时候学习过 当我们使用prop
进行参数传递的时候,父组件数据的改变会影响子组件 。这样,我们在com-1
中count
的变化就会在com-2
中被展示出来。
在我们的Demo
事例中,通过这种方法来维护count
的状态未尝不可。不过大家想一下,在我们的实际项目中各个组件的层级会变得非常复杂,同时对于这种数据的状态管理如果都通过这种组件之间传参的方式来进行解决的话,则会变得非常难以维护。那么我们就会想有没有其他的方式可以解决这个问题?那就是采用全局状态管理的方法。
我们看一下下面的代码:
<div id="app">
<com-1></com-1>
<com-2></com-2>
</div>
<script type="text/x-template" id="com-1">
<div>
<input type="button" @click="addCount" value="count++">
</div>
</script>
<script type="text/x-template" id="com-2">
<div>
count: {{count}}
</div>
</script>
<script>
var store = {
count: 0
};
Vue.component('com-1', {
template: '#com-1',
data: function () {
return store
},
methods: {
addCount: function () {
this.count += 1;
}
}
});
Vue.component('com-2', {
template: '#com-2',
data: function () {
return store
},
});
var vm = new Vue({
el: '#app',
});
</script>
我们看到在上面的代码中我们维护了一个store
对象,用作两个组件中原始数据对象的实际来源 当访问数据对象时,一个 Vue 实例(组件实例)只是简单的代理访问。 所以,如果你有一处需要被多个实例间共享的状态,可以简单地通过维护一份数据来实现共享。现在当 store
发生变化,com-1
和com-2
都将自动的更新引用它们的视图。从而达到了我们需求,当我们在com-1
中点击count++
的时候在com-2
中展示count
的值。
但是对我们现在的代码来说,依然存在一个非常致命的问题,那就是 在任何时间,我们应用中的任何部分,在任何数据改变后,都不会留下变更过的记录。这在我们想要进行代码调试的时候,将会变成一个噩梦。所以我们可以采用一个简单的store模式来解决这个问题:
<div id="app">
<com-1></com-1>
<com-2></com-2>
</div>
<script type="text/x-template" id="com-1">
<div>
<input type="button" @click="addCount" value="count++">
</div>
</script>
<script type="text/x-template" id="com-2">
<div>
count: {{sharedState.count}}
</div>
</script>
<script>
var store = {
debug: true,
// 数据 / 状态
state: {
count: 0
},
// state的变化要通过mutation来进行
mutation: {
addCount: function (state) {
if (store.debug) console.log('调用addCount方法,count自加1');
state.count += 1;
}
}
};
Vue.component('com-1', {
template: '#com-1',
data: function () {
return {
// 私有数据
privateState: {
},
// 共同数据
sharedState: store.state
}
},
methods: {
addCount: function () {
// 调用
store.mutation.addCount(store.state);
}
}
});
Vue.component('com-2', {
template: '#com-2',
data: function () {
return {
// 私有数据
privateState: {
},
// 共同数据
sharedState: store.state
}
},
});
var vm = new Vue({
el: '#app',
});
</script>
在上面的代码中我们为store
增加了debug、state、mutation
三个属性,state
是store
对象中所有状态数据的描述,当state
中的数据想要发生改变的时候都要通过mutation
来进行。比如我们的count++
的点击事件,则是通过调用store.mutation.addCount
方法进行,以此来记录数据的改变。
这样的一种方式在Vue
中被称为**store
模式**,我们使用**store
模式作为我们整个项目的状态管理器**,但是当我们的项目变得越来越大,越来越复杂的时候,我们更需要一个更加专业,更加全面的状态管理器,那么这个状态管理器就是Vuex
。
Vuex
Vuex
继承了store模式
的思想,并做了更多的扩展,提供了更多的功能。我们先来看一下Vuex
的定义:
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。致力于管理项目中所有公用数据的状态。
我们参照上面的几个例子,是不是对Vuex
的定义更加容易理解。Vuex
是在store模式
上的扩展,它们拥有相同的思想。我们看一下Vuex
中的核心功能。
1、State :
Vuex
中的 “ 唯一数据源 ”
2、Getter :Vuex
中的计算属性,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
3、Mutation :更改Vuex
中数据的唯一方式,就是通过mutation
进行修改,只支持同步操作。就像我们上面的例子一样。
4、Action :当我们需要异步更改数据时,通过Action
提交的是mutation
,而不是直接变更状态。
5、Module : 由于Vuex
使用单一状态树,应用的所有状态会集中到一个比较大的对象。Vuex
允许我们将store
分割成模块(module
)。每个模块拥有自己的state、mutation、action、getter
甚至是嵌套子模块——从上至下进行同样方式的分割。
由前面的例子我们也可以看出,我们不希望state
中的状态被直接改变(虽然我们可以这么做),而是期望把Mutation
作为改变state
的唯一方式,而当我们必须要通过异步来改变状态的时候,我们期望使用action
来提交mutaion
用于改变状态。大家思考一下Vuex
为什么要这么去设计呢?
在Vuex
中说白了,任何的操作都是围绕state
来进行的,Vuex
是状态管理器,作用就是管理state
中的状态,其他提供的所有功能Getter、Mutation、Action
都是为了能够更好的管理state
,而之所以设计成期望通过Mutation
改变状态,是因为我们期望**所有状态的变化都是有迹可循的!**我们通过一个图示来看一下Vuex
的设计流程(图示来自Vuex官网):
图示完美的解释了Vuex
的执行流程:我们在Vue
的组件中提交Dispatch
操作,用以操作对应的Actions
方法,然后在Actions
的方法中,通过提交Commit
操作调用对应的Mutation
方法来修改对应的状态State
,最后State
的改变被渲染到我们的组件中。这就是Vuex
的整个核心流程,我们通过一段代码来描述一下:
<div id="app">
<com-1></com-1>
<com-2></com-2>
</div>
<script type="text/x-template" id="com-1">
<div>
<input type="button" @click="addCount" value="count++">
</div>
</script>
<script type="text/x-template" id="com-2">
<div>
count: {{this.$store.state.count}}
</div>
</script>
<script>
/**
* 声明一个Vuex实例,这个实例全局只有一个
* */
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
/**
* Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type)
* 和 一个 回调函数 (handler)。
* 这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:
* */
increment (state, num) {
// 变更状态
state.count += num;
}
},
actions: {
/**
* Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,
* 因此你可以调用 context.commit 提交一个 mutation,
* 或者通过 context.state 和 context.getters 来获取 state 和 getters。
* 但是 context 对象并!不!等!于!store 实例本身
* */
increment (context, num) {
// 延迟1秒提交commit操作
setTimeout(function () {
/**
* 你不能直接调用一个 mutation handler。
* 这个选项更像是事件注册:“当触发一个类型为 increment 的 mutation 时,调用此函数。
* ”要唤醒一个 mutation handler,你需要以相应的 type 调用 store.commit 方法.
* 你可以向 store.commit 传入额外的参数,即 mutation 的 载荷(payload)。
* 或者我们也可以通过 对象风格的提交方式:
* store.commit({
type: 'increment',
num: 1
})
这种情况下我们将在 mutation 中接收到一个对象
* */
context.commit('increment', num)
}, 1000);
}
}
})
Vue.component('com-1', {
template: '#com-1',
methods: {
addCount: function () {
/**
* 我们可以直接向 store.dispatch 传入额外的参数,即 action 的 载荷(payload)。
* 或者我们也可以通过 对象风格的提交方式:
* store.dispatch({
type: 'increment',
num: 1
})
这种情况下我们将在 action 中接收到一个对象
* */
this.$store.dispatch('increment', 1);
}
}
});
Vue.component('com-2', {
template: '#com-2',
});
var vm = new Vue({
el: '#app',
store , // 等同于 store:store
});
</script>
在上面的代码中,我们首先通过new Vuex.Store
去声明了一个Vuex
的实例store
,当我们点击count++
按钮的时候,调用this.$store.dispatch('increment', 1);
其实是调用了Vuex
中actions
中定义的increment
方法,在这个方法中我们声明了一个setTimeout
,指定在1秒钟之后执行context.commit('increment', num)
方法,调用mutation
的increment
方法,然后我们在mutation
的increment
方法中修改了state
中的count
的值,使其state.count += num;
。这样的一个执行流程就是我们Vuex
中所提倡的执行方法,也是我们上方图示中所描述的执行流程。
我们知道state、mutaion、action
是整个Vuex
最核心的内容,在它们之外,Getter、Module
也是我们必须要了解的内容。我们看下面的代码:
...
<script type="text/x-template" id="com-2">
<div>
<!-- 我们将要通过模块的路径来调整 state的命名 -->
<p>count: {{this.$store.state.a.count}}</p>
<!-- Getter 会暴露为 store.getters 对象,你可以以属性的形式访问这些值: -->
<p>doubleCount: {{this.$store.getters.doubleCount}}</p>
</div>
</script>
<script>
/**
* 声明一个Vuex模块
* 默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的
* ——这样使得多个模块能够对同一 mutation 或 action 作出响应。
* 而 state 则是模块的局部状态对象!
*
* 如果希望你的模块具有更高的封装度和复用性,
* 你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。
* 当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。
* */
const moduleA = {
// namespaced: true,
state: {
count: 1
},
getters: {
/**
* Getter 接受两个参数,
* 第一为:state 状态对象
* 第二位:其他的 getter
* */
doubleCount: function (state, getters) {
console.log('getters.count: ' + getters.count);
return state.count * 2;
},
count: function (state) {
return state.count;
},
},
mutations: {
increment (state, num) {
// 这里的 `state` 对象是模块的局部状态
state.count += num;
}
},
...
};
/**
* 声明一个Vuex实例,这个实例全局只有一个
* */
const store = new Vuex.Store({
modules: {
a: moduleA
}
});
...
</script>
在上面的代码中,我们只贴出了关键性代码,我们把Vuex
进行了模块化的区分,提供了moduleA
用以描述state、getters、mutations
等,具体的作用在上面的代码中有详细的注释,我们这里就不在过多叙述了。
总结
在本章的内容中,我们并没有一头扎进Vuex
的细节中,因为对Vuex
来说,它的具体使用方式比较容易,只要我们能够理解它的本质作用,它的执行流程那么在我们使用Vuex
的时候就会水到渠成。
除去我们讲到的Vuex
的核心功能之外,Vuex
还提供了一些其他的功能,比如plugins插件
,这些内容在Vuex官网中介绍的非常清楚,我们这里就不想在进行重复介绍了。
···································
欢迎关注课程: