1.什么是Vuex?
官方回答:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
2.Vuex能解决什么问题?
当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:
- 多个视图依赖于同一状态。
- 来自不同视图的行为需要变更同一状态。
对于问题一,传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力。
对于问题二,我们经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式非常脆弱,通常会导致无法维护的代码。
因此,把组件的共享状态抽取出来,以一个全局单例模式管理。在这种模式下,我们的组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为!
通过定义和隔离状态管理中的各种概念并通过强制规则维持视图和状态间的独立性,我们的代码将会变得更结构化且易维护。
3.创建Vuex的步骤:
- 下载并引用vuex.js文件,插入到vue.js文件后面
- 创建Vuex实例对象
const store = new Vuex.Store({
state:{
msg:"鱿小鱼"
},
mutations:{},
actions:{},
getters:{}
});
- 在祖先组件中添加store的key保存Vuex对象; 只要祖先组件中保存了Vuex对象 , 那么祖先组件和所有的后代组件就可以使用Vuex中保存的共享数据了
Vue.component("father",{
template:"#father",
//在祖先组件中添加store的key保存Vuex对象
store:store,
data:function(){
return{}
},
methods:{},
computed:{},
components:{}
})
- 引用vuex中的共享数据方法
<template id="father">
<div>
<p>{{this.$store.state.msg}}</p>
<son></son>
</div>
</template>
4.State
- state属性和组件的data属性类似;state作用:用来
保存
全局共享数据
在引入vuex 以后,我们需要在创建Vuex对象的state中定义变量:
const store = new Vuex.Store({
// 这里的state就相当于组件中的data, 就是专门用于保存共享数据的
state:{
msg:"鱿小鱼"
}
});
组件中内容:
<div id="app">
<father></father>
</div>
<template id="father">
<div>
<!--4.在使用Vuex中保存的共享数据的时候, 必须通过如下的格式来使用-->
<p>{{this.$store.state.msg}}</p>
<son></son>
</div>
</template>
<template id="son">
<div>
<p>{{this.$store.state.msg}}</p>
</div>
</template>
有了vuex,我们不必在考虑组件之间的传值,直接就可以通过$store来获取不同的数据,,但是如果需要vuex中的多个数据的这时候,这样写就太啰嗦了,我们可以将它定义在computed中。
<template id="son">
<div>
<p>{{msg}}</p>
</div>
</template>
Vue.component("father", {
template: "#father",
// 在祖先组件中添加store的key保存Vuex对象
// 只要祖先组件中保存了Vuex对象 , 那么祖先组件和所有的后代组件就可以使用Vuex中保存的共享数据了
store:store,
// 子组件
components: {
"son": {
template: "#son",
computed: {
nickname(){
return this.$store.state.msg
}
}
}
}
});
这样引入就方便了很多。
-
mapState 辅助函数
mapState辅助函数作用:可以辅助获取到多个state的值
怎么使用?
1.Vuex实例对象部分:
state:{
msg:"鱿小鱼"
}
2.在.vue组件中引入,在js块中引入
import { mapState } from 'vuex'
3.在vue组件computed中定义一个数组
computed:{
/*通过mapState将 store 中的 state 的全局共享数据名称 “映射”到“局部计算属性”*/
...mapState([ //mapState本是一个函数,在里面写一个数组,记得加...
'msg' //存的数据,把 `this.msg` 映射为 `this.$store.state.msg`
])
}
这一句代码就相当于下面这句:
computed:{
msg(){
return this.$store.state.msg
}
}
4.然后就可以不用$store.state.msg
引用了,直接插值
{{msg}}
5.Getters
-
getters和computed相似;getters的作用:用来保存
获取
共享数据的计算属性方法
就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算
补充:
- 什么是计算属性?
将计算结果缓存起来,只要数据没有发生变化就只会计算一次,以后使用的都是缓存>起来的数据- 函数和计算属性的区别?
函数的特点:每次调用都会执行
计算属性的特点:只要返回的结果没有发生变化,那么计算属性只会被执行一次data:{ message:"abcdefg" }, //5.专门用于储存监听事件回调函数 methods:{ /*函数的特点:每次调用都会执行*/ msg1(){ console.log("msg1被执行了"); let res = this.message.split('').reverse().join(''); return res; } }, //6.专门用于定义计算属性的 computed: { /*计算属性的特点:只要返回的结果没有发生变化,那么计算属性只会被执行一次 计算属性应用的场景:由于计算属性会将返回的结果缓存起来 如果返回的数据不会频繁的发生改变, 使用计算属性会比函数的性能高 */ // 计算属性的 getter reversedMessage: function () { console.log("reversedMessage被执行了"); // `this` 指向 vm 实例 let res = this.message.split('').reverse().join(''); return res; } }
- 如何把数据缓存起来?
这个数据如果是组件中的,就使用computed来缓存
这个数据如果是Vuex中的,就使用getters来缓存效果:<div id="app"> <father></father> </div> <template id="father"> <div> <p>{{this.$store.getters.format}}</p> <p>{{this.$store.getters.format}}</p> <p>{{this.$store.getters.format}}</p> </div> </template> <script type="text/javascript"> const store = new Vuex.Store({ state:{ name:"鱿小鱼" }, mutations:{}, getters:{ format(state){ console.log("getters被调用了") return state.name + "2541873074@qq.com" } } }) Vue.component("father",{ template:"#father", store:store, }) let vue= new Vue({ el:"#app", }) </script>
- getters相当于vue中的计算属性,通过getters进一步处理,得到我们想要的值,而且允许传参;
第一个参数就是
state`:
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
}
}
})
通过属性访问:Getter 会暴露为 store.getters 对象,你可以以属性的形式访问这些值
store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]
Getter 也可以接受其他 getter
作为第二个参数:
getters: {
// ...
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
}
}
通过属性访问:
store.getters.doneTodosCount // -> 1
通过方法访问:可以通过让 getter 返回一个函数,来实现给 getter 传参。在你对 store 里的数组进行查询时非常有用。
getters: {
// ...
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
}
}
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }
-
mapGetters 辅助函数
mapGetters 辅助函数的作用:仅仅是将 store 中的 getter 映射到局部计算属性
怎么使用?
1.Vuex实例对象部分:
state:{
count:0
},
// getters:获取全局共享的数据
getters: {
count (state) {
return state.count
}
}
2.在.vue组件中引入,在js块中引入
import { mapGetters} from 'vuex'
3.在vue组件computed中定义一个数组:
computed: {
// 使用对象展开运算符将 getter 混入 computed 对象中
...mapGetters([
'count ', // 把 `this.count` 映射为 `this.$store.getters.count`
// ...
])
}
以上代码就相当于下面的代码:
computed: {
count(state){
return this.$store.getters.count
}
}
6.Mutations
- mutations和组件中的methods属性类似;mutations作用:用来保存
修改
全局共享数据的方法
<div id="app">
<father></father>
</div>
<template id="father">
<div>
<son1></son1>
</div>
</template>
<template id="son1">
<div>
<button @click="add">增加</button>
<button @click="sub">减少</button>
<input type="text" :value="this.$store.state.count">
</div>
</template>
<script>
const store = new Vuex.Store({
// 这里的state就相当于组件中的data, 就是专门用于保存共享数据的
state: {
count:0
},
// mutations:用于保存修改共享数据的方法的
mutations: {
/*
注意点:在执行mutations中定义的方法的时候, 系统会自动给这些方法传递一个state参数
state中就保存了共享的数据
*/
mAdd(state){
state.count = state.count + 1;
},
mSub(state){
state.count = state.count - 1;
}
}
});
Vue.component("father", {
template: "#father",
store:store,
//子组件
components: {
"son1": {
template: "#son1",
methods:{
add(){
//注意点:在Vuex中不推荐直接修改共享数据
// this.$store.state.count = this.$store.state.count + 1;
this.$store.commit("mAdd");
},
sub(){
// this.$store.state.count = this.$store.state.count - 1;
this.$store.commit("mSub");
}
}
}
}
});
let vue = new Vue({
el: '#app',
});
</script>
- mutations需要通过
commit
来调用其里面的方法,它也可以传入参数,第一个参数是state
,第二个参数是载荷(payLoad)
,也就是额外的参数
Vuex实例对象部分:
state:{
count:0
},
mutations: { //类似于methods
changecount(state,payLoad){ //第一个参数是`state`,第二个参数是`载荷(payLoad)`,也就是额外的参数
state.count += payLoad.number
}
}
template模板部分:
<div class="home">
<div><button @click="test">我是按钮</button></div>
</div>
Vue实例对象部分:
methods:{
test(){
this.$store.commit(' changecount',{ //第二个参数最好写成对象形式
number:5
})
}
}
调用的时候第二个参数最好写成对象形式,这样我们就可以传递更多信息。
但是,这样写还是会遇到同样的问题,就是如果需要操作多个数据,就会变的麻烦,这时候我们就需要mapMutations辅助函数
,通过它将mutations
中方法映射到组件的methods
属性中
这里是通过
mapMutations
将mutations
中方法映射组件的methods
属性,需要提前做的准备和须知:
- 使用常量替代 Mutation 事件类型
① 新建一个mutations-type.js文件:
// mutation-types.js export const CHANGE_COUNT = 'CHANGE_COUNT'
② store.js文件中:
// store.js import Vuex from 'vuex' import { CHANGE_COUNT } from './mutation-types' const store = new Vuex.Store({ state: { count:0 }, //这里在做项目时也可以把mutation中代码单独提取到一个文件中,命名为mutatons.js文件 mutations: { // 我们可以使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名 [CHANGE_COUNT] (state,payLoad) { // payLoad 传递的值 // mutate state state.count = payLoad } } })
为什么要使用常量替代 Mutation 事件类型?
因为在Actions中
通过commit('changecount', playLoad)
触发Mutations
中changecount
修改数据的方法时,用的是“”
将changecount
包裹成字符串,由于字符串在书写过程中不会报错,所以将Mutations
中的方法名定义为常量,这样在书写不正确时会报错,方便维护使用常量之
前
在actions
中调用mutations
中方法://这里在做项目时也可以把actions中代码单独提取到一个文件中,命名为actions.js文件 actions: { //用到 ES2015 的 参数解构 来简化代码(特别是我们需要调用 `commit` 很多次的时候) setchangecount({ commit },playLoad){ commit('changecount',playLoad) } }
使用常量之
后
在actions
中调用mutations
中方法://这里在做项目时也可以把actions中代码单独提取到一个文件中,命名为actions.js文件 import {CHANGE_COUNT} from './mutations-type' actions: { //用到 ES2015 的 参数解构 来简化代码(特别是我们需要调用 `commit` 很多次的时候) setchangecount({ commit },playLoad){ commit(CHANGE_COUNT,playLoad) } }
- Mutation 必须是同步函数
一条重要的原则就是要记住mutation
必须是同步函数。
下面Vue官网原话:mutations: { someMutation (state) { api.callAsyncMethod(() => { state.count++ }) } }
现在想象,我们正在 debug 一个 app 并且观察 devtool 中的
mutation
日志。每一条mutation
被记录,devtools 都需要捕捉到前一状态和后一状态的快照。然而,在上面的例子中mutation
中的异步函数中的回调让这不可能完成:因为当mutation
触发的时候,回调函数还没有被调用,devtools 不知道什么时候回调函数实际上被调用——实质上任何在回调函数中进行的状态的改变都是不可追踪的。
-
mapMutations辅助函数
mapMutations辅助函数的作用:在组件中调用mutaions
中的方法;
原理: 就如同,通过store.commit(mutations中保存的修改state数据的方法)
调用mutaions
中的方法
怎么使用?
1.Vuex实例对象部分:
state:{
count:0
},
mutations: { //类似于methods
changecount(state,payLoad){
state.count = payLoad
}
}
2.在.vue组件中引入,在js块中引入
import { mapMutations } from 'vuex'
3.在vue组件methods中定义一个数组(Vue实例对象部分):
methods: {
...mapMutations([
'changecount', // 将 `this.changecount()` 映射为 `this.$store.commit('changecount')`
])
}
以上代码就相当于下面的代码:
methods: {
changecount(playLoad){
this.$store.commit('changecount',playLoad)
}
}
5.参数我们可以在调用这个方法的时候写入
<button @click="changecount({number:5})">我是按钮</button>
5.使用常量之前
在actions
中调用mutations
中方法:
//这里在做项目时也可以把actions中代码单独提取到一个文件中,命名为actions.js文件
actions: {
setchangecount({ commit },playLoad){
commit('changecount',playLoad)
}
}
- 为什么要绕一圈,从mutations里面去改state呢?我能不能直接改state呢?
比如这样:
changecount(){
this.$store.state.count +=5;
}
实际看结果也可以,那我为什么从mutations里面中转一下呢?以下的解释是我看别人写的,写的不错就在这里利用一下
原因如下:
① 在mutations中不仅仅能做赋值操作
② 作者在mutations中做了类似埋点操作,如果从mutations中操作的话, 能被检测到,可以更方便用调试工具调试,调试工具可以检测到实时变化,而直接改变state中的属性,则无法实时监测
注意:mutations只能写同步方法,不能写异步,比如axios、setTimeout等,这些都不能写,mutations的主要作用就是为了修改state的。
原因类似:如果在mutations中写异步,也能够调成功,但是由于是异步的,不能被调试工具追踪到,所有不推荐这样写,不利于调试,这是官方的约定。
7.Actions
- action类似于mutation;actions的作用:保存
触发
Mutations中修改全局共享数据的方法
不同在于:
action
提交(commit
)的是mutation
,而不是直接变更状态action
包含异步操作,类似于axios请求,可以都放在action
中写action
中的方法默认的就是异步,并且返回promise
代码如下:
sate.js
export default {
count: 0
}
mutations-typa.js
export const CHANGE_COUNT = 'CHANGE_COUNT'
mutations.js
import {CHANGE_COUNT} from './mutations-type'
export default {
[CHANGE_COUNT] (state,payLoad) {
state.count = payLoad
}
}
actions.js
import {CHANGE_COUNT} from './mutations-type'
export default {
setchangecount (context) {
context.commit(CHANGE_COUNT)
}
}
Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit
提交一个 mutation,或者通过 context.state
和 context.getters
来获取 state 和 getters。当我们在之后介绍到 Modules 时,你就知道 context 对象为什么不是 store 实例本身了。
- 实践中,我们会经常用到 ES2015 的 参数解构 来简化代码(特别是我们需要调用
commit
很多次的时候):
actions: {
//用到 ES2015 的 参数解构 来简化代码(特别是我们需要调用 `commit` 很多次的时候)
setchangecount ({ commit } , payLoad) {
commit(CHANGE_COUNT , payLoad)
}
}
- 在action内部可以执行异步操作:
actions: {
setchangecount ({ commit }) {
setTimeout(() => {
commit(CHANGE_COUNT)
}, 1000)
}
}
-
mapActions 辅助函数
mapActions 辅助函数的作用: 将actions
中的方法映射到组件中;
原理:就如同,通过store.dispatch(actions保存的触发mutation中的方法)
映射actions
中的方法
分发Action的两种方式:
分发Action:也就相当于在界面中触发Action方法
1.Action 通过 store.dispatch
方法触发:
store.dispatch('setchangecount')
2.在组件中分发 Action:使用 mapActions
辅助函数将组件的 methods 映射为 store.dispatch
调用
如何使用mapActions 辅助函数?
1.Vuex实例对象部分:
state:{
count:0
},
//这里并没有使用常量替代 Mutation 事件
mutations: { //类似于methods
changecount(state,payLoad){
state.count = payLoad
}
}
actions: {
setchangecount ({ commit } , payLoad) {
commit('changecount' , payLoad)
}
}
2.在.vue组件中引入,在js块中引入
import { mapActions } from 'vuex'
3.在vue组件methods中定义一个数组(Vue实例对象部分):
methods: {
...mapActions([
'setchangecount', // 将 `this.setchangecount()` 映射为`this.$store.dispatch('setchangecount')`
]),
}
上面的代码就相当于下面的代码:
methods: {
setchangecount(){
return this.$store.dispatch('setchangecount')
}
}
- Actions 支持同样的载荷方式和对象方式进行分发
// 以载荷形式分发
store.dispatch('setchangecount', {
amount: 10
})
// 以对象形式分发
store.dispatch({
type: 'setchangecount',
amount: 10
})