为什么会出现vuex
vuex的应用场景,大家可能或多或少知道,用来管理一些全局数据,或者多个页面都会使用到的数据,比如,我们项目中常见的用户信息,通常使用vuex来管理。
这里,我们思考一个问题,如何没有vuex,还有哪些方式可以实现一些公共数据的管理?
- 把公共数据存储到根实例中,其他页面都可以通过 this.$root 来获取根实例中的数据。
- 把公共数据存储到根实例中,其他页面通过 provide/inject 机制来获取根实例中的数据。
以上两种方式,虽然可以用来管理我们的全局数据,但是随着项目越来越复杂,全局数据越来越多,目前这样的管理方式成本还是有些高,所以,我们需要一套系统化的,流程化的机制来管理全局数据,而vuex就是为此而诞生的。
vuex的优点:它不仅仅是一个全局数据的管理器,同时它最大的特性是支持响应式的,也就是说使用vuex管理的数据如果发生改变,那么引入这些数据的组件都会自动刷新,
运行机制
首先,我们通过上图要明白以下几点:
- 整个流程依然是单项数据流
- 从State开始,这里存储着我们所需要的全局变量,同样读取的时候,我们也是从State里面读取数据。
- State里面的数据变更,Vue component也就是vue组件,即页面会进行响应式变化。
- 数据更新方式1: 通过commit操作,在Mutations中进行数据更新
- 数据更新方式2: 通过dispatch操作,在actions中进行数据更新(actions内部其实本质上也是通过commit在mutations中更新数据的)
- actions中通常可以进行一些异步操作,所以如果有一些数据需要通过后端接口获取,那么通常将相关代码逻辑放到actions去处理
- mutations中都是同步操作,如果只有同步操作,则相关逻辑直接在mutations中处理即可
以上基本覆盖了vuex的常用知识点,接下来,我们来通过一个具体的代码案例来深入理解一下。
vuex基本使用
我们来实现一个简单的计数器,点击可以增加数字, 首先,本次代码都在基于vue-cli3生成的项目模版去实现的。
第一步:安装vuex
npm i vuex --save
第二步:引入vuex
//main.js
import App from './App.vue'
Vue.config.productionTip = false
//vuex的使用
import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store({
//所有的数据都存放在state中
state: {
count: 0
},
//mutations实现同步操作
mutations: {
increment (state, params) {
state.count += params;
}
},
//actions中实现一些异步操作
actions: {
increment ({state}, params) {
setTimeout(() => {
state.count += params;
}, 3000)
}
},
getters: {
doubleCount (state) {
return state.count * 2;
}
}
});
new Vue({
store,
render: h => h(App),
}).$mount('#app')
//app.vue
<template>
<div id="app">
<span>count: {{count}}</span>
<button @click="addCount1">count++</button>
<br>
<span>doubleCount: {{doubleCount}}</span>
<button @click="addCount2">count++</button>
</div>
</template>
<script>
export default {
name: 'app',
computed: {
count () {
return this.$store.state.count;
},
doubleCount () {
return this.$store.getters.doubleCount;
}
},
methods: {
addCount1 () {
this.$store.commit('increment', 2); //mutations通过commit触发
},
addCount2 () {
this.$store.dispatch('increment', 2); //actions通过dispatchc触发
}
}
}
</script>
通过上面的代码,我们要清楚以下几点:
- State: this.$store.state.xxx 取值
- Getters: this.$store.getters.xxx 取值
- Mutations: this.$store.commit('xxx') 赋值
- Action:this.$store.dispatch('xxx') 赋值
也就是说:我们通常可以通过state和getters来获取全局数据,也可以通过mutations和actions修改全局数据,mutations是同步操作,actions是异步操作。
1. store的创建与注入
- 创建store
import Vuex from 'vuex';
const store = new Vuex.Store({
state: {},
mutations: {}
...
});
- 向vue实例中注入store
new Vue({
store,
render: h => h(App),
}).$mount('#app')
问题:
- Vue.use(Vuex);//use方法的原理
- 将store注入到vue实例的原理
2. State
通过上面的学习,我们知道了,所有状态变量都是定义在store中的state属性中,那么,如何获取state中的数据呢? //第一种方式:
export default {
computed: {
count () {
return this.$store.state.count;
},
doubleCount () {
return this.$store.getters.doubleCount;
},
number () {
return this.$store.state.number;
}
},
}
即直接在computed计算属性中返回所需数据,缺点就是:如果状态树越来越多,每个属性都需要我们在computed中声明并且返回一次,不太友好,解决方案就是mapState
//第二种方式:
import {mapState} from 'vuex';
export default {
computed: mapState([
'count',
'doubleCount',
'number'
]),
}
同时,如果想自定义别名,也可以给mapState传入一个对象,自定义处理,但是上面的写法,我们就无法在computed声明当前vue实例下自己的计算属性了,解决方案就是rest运算符...
//第三种方式:
import {mapState} from 'vuex';
export default {
computed: {
...mapState([
'count',
'number'
]),
...mapGetters([
'doubleCount'
]),
//也可以自定义其他计算属性
},
}
通过上面,我们知道了可以有很多种不同的写法去读取state里面的数据,getters也类似,上面第三种方式也有体现,我们需要知道各种写法的优缺点,同时,在实际项目中,我们只需要采用最优写法即可,即第三种方式。
3. Getter
Getter与State的作用都是定义一些全局使用的数据,Getter和State的关系就类似于computed和data的关系,Getter的返回值会被缓存起来,只有它所依赖的state中的数据发生变化才会跟着重新计算。
我们只需要明白Getter的作用以及与State的区别即可,写法和State类似,参考上面即可。
4. Mutation
上面我们知道了,可以通过state和getter来定义和读取状态,那么接下来,如何修改状态呢?记住一句话:更改 Vuex 的 store 中的状态的唯一方法是提交 mutation.
首先,要说明一下mutation中的几个概念:
- 事件类型
- 载荷payload
我们在具体的代码中说明:
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
//increment就是在mutation中定义的事件类型
increment (state, payload) {
state.count += payload.amount
}
}
})
store.commit('increment', {
amount: 10 // commit中传的参数就是载荷
})
接下来,我们要说一下在组件中mutation的几种写法: //第一种
export default {
methods: {
addCount () {
this.$store.commit('increment', 2);
}
}
}
//第二种方式:
import {mapMutations} from 'vuex';
export default {
methods: {
...mapMutations([
'increment'
]),
addCount () {
this.increment(2);
}
}
}
注意点:在mutation中都是同步事件,因为mutation的作用就是可以捕捉到每一次的提交或者修改记录,如果是异步事件,devtool很难捕捉到,这时就要用到Action了
5. Action
Action和Mutation都是用来修改状态的,区别就是action中通常是一些异步事件,并且通过dispatch去分发事件,当然,有一点,我们要知道actions内部也是通过提交mutation去修改数据的。
这里,具体写法就不说了,和mutation类似。
6. 其他注意点:
通过上面的讲解,我们都知道了,修改state的唯一路径就是 提交mutation,当然,如果我们直接修改了state里面的值,在非严格模式下,也是可以生效的,但是不建议这么做,在严格模式下,vue会直接报错
还有一点:表单里使用v-model双向绑定的如果是state里面的数据,这时,如果在严格模式下,会直接报错的,因为数据修改完,v-model内部就默认直接修改了state里面的数据,具体解决方案,可以直接参考官网:vuex.vuejs.org/zh/guide/fo…
7. 总结
vuex其实本身内容是比较简单的,但是由于我们引入mapState,mapGetter等map写法之后,可能会容易产生混淆,导致感觉vuex相关概念较多,无法真正理清,这里我们在总结一下:
首先vuex的核心就是store,也叫仓库,他就是一个容器,里面包含着state,getter,mutation,action等属性,
- 首先这些状态都定义在哪里呢?
即直接定义在state中就可以啦
- 定义完以后,怎么在组件中读取状态呢?
通过this.store.getter的方式去读取,当然为了写法更加间接,我们引入了mapState和mapGetter。
- 定义完以后,怎么在组件中修改状态呢?
vuex只支持一种方式:那就是commit mutation,action内部本身也是commit mutaition, 当然,依然是为了写法间接,我们又引入了mapMutations和mapActions。
通过上面的总结,我们知道了map系列api,都只是为了如何更简洁的读取和修改状态,暂时抛开这一部分,我们发现,其实vuex的内容其实很简单。
原理解析
这一节,我们主要看一下vuex的实现原理,同时,实现也可以简易版的vuex。
通过上面的介绍,我们知道了vuex中的状态state和单纯的全局对象还是不同的,主要体现在:
- vuex的状态是响应式的
- vuex的状态不可以直接修改,必须通过提交mutation的方式去修改。
那,我们接下来看看vuex具体是如何实现响应式的?其实内部就是通过vue的响应式机制去实现的。
下面,我们可以手动实现一个简易版的vuex
import Vue from 'vue'
const Store = function Store (options = {}) {
const {state = {}, mutations={}} = options
this._vm = new Vue({
data: {
$$state: state
},
})
this._mutations = mutations
}
Store.prototype.commit = function(type, payload){
if(this._mutations[type]) {
this._mutations[type](this.state, payload)
}
}
Object.defineProperties(Store.prototype, {
state: {
get: function(){
return this._vm._data.$$state
}
}
});
export default {Store}
通过上面的代码,我们看到,其实vuex内部也是通过new Vue()的方式,将state存储在vue实例的data中,从而实现响应式的。