vue开始的组件化开发,同时基于spa(single page app)使得前端项目呈现出组件树结构,这就自然而然的带来一个问题“组件间怎么通信?”。
虽然我们可以通过$emit
,和props实现组件间的简单通信,但是对于跨组件通信就显得很笨重。
这时候有人建议,“我们可以定义一个新的vue,这个vue用于传输数据”,通过vue实现的消息订阅和监听确实可以实现跨组件通信,不过设想一个和你起名字风格一样的人,不幸的他器的名字和你起得一致。。。这就是一个隐形的灾难了。
所以我们需要一种可以帮助我们管理数据状态,同时在变化的时候可以及时通知我们的东西。
VueX就此产生了:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
为了解决以上问题,有人提出了单向数据流的概念
单向数据流概念:
我们通过视图层,看到需要修改的数据,从而产生事件(action),这时候可以是网页内部的修改也可以是向后端请求数据,这时候数据并不是直接对用户展示的,而需要进行一个状态的切换;我们可以当这个state是一个统一修改的端口,所有修改的数据都应该经过这里,这样的结果就是所有的数据我们都可以进行追溯。
但这个模型却遇到了一些问题(一下取自VueX官网)
但是,当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:
对于问题一,传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力。对于问题二,我们经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式非常脆弱,通常会导致无法维护的代码。
总结:我们对于数据请求有了管理的思路和理论模型,不过对具体的实施却遇到了阻碍。
借助 Flux、Redux 和 The Elm Architecture的思想VueX呼之欲出。
基于单项数据流的思想,VueX也提出了自己的一套实施方案。
vue组件修改数据,执行了相关的方法,数据返回到达了action阶段,这时候vue组件并没有产生修改的事件,这时候时候通过commit对数据进行提交,将数据的状态变为mutation。这时候通过调用mutate将修改的数据状态写入。这时候由于存在mvvm,视图层的数据就会产生变化。
vuex的使用流程:
首先明确vuex是实现单向数据流的一个手段,单项数据流的本质就是修改只通过一个位置,这样可以对数据的变更记性追踪。
页面里通过mapActions做了映射
...mapActions([
'fetchAreas',
'fetchCrops',
'fetchTasks',
'getInformation',
]),
这里的这些方法都是写在methods里面的,也就是可以当做正常的方法去使用。
mounted() {
this.fetchAreas();
this.getCrops();
this.getTasks();
this.getInformation();
},
这些方法都有了,这里只是做映射,所以下面并不需要在写一遍方法的内容。
这个方法是怎么来的呢?
这里面使用的映射方法是mapActions,这个属于vuex的方法。
import { mapGetters, mapActions } from 'vuex';
所以这个方法是存在了vuex中。
小结:该用法是通过初始化调用了vuex里面的方法,实现数据的初始化请求。
既然是vuex的东西,那就必然是store模块里面action的方法(原本应该写成dispatch,调用但是因为有map做了映射就省去了)。
我们看看action里面:
// 这里请求了状态 提价方式等,
fetchAreas({ commit, state, getters }, payload) {
// 获得当前的农场,这里可以看出即便是在这个里面依然可以使用getter方法,只要通过第一个参数引入即可。
const farm = getters.getCurrentFarm;
// 进度条开启
NProgress.start();
// promise是一个异步的函数,如果成功就执行resolve,如果失败就执行resolve。
return new Promise((resolve, reject) => {
FarmApi.ApiFetchArea(farm.uid, ({ data }) => {
commit(types.FETCH_AREA, data.data);
resolve(data);
}, error => reject(error.response));
});
},
这里牵扯到了promise函数。
这是个异步的函数,可以通过执行then使得函数向后进行,走到resolve(data);算是成功。
FarmApi.ApiFetchArea
这个是个接口,即与后端交互的位置。
ApiFetchArea: (farmid, cbSuccess, cbError) => {
http.get('farms/' + farmid + '/areas', cbSuccess, cbError)
},
小结:也就是说ApiFetchArea
,会返回两个函数,一个是成功时候的,一个是失败时候的。成功的函数会执行promise的resolve方法,失败了会执行reject方法。
从通信放回回来以后 success执行了commit方法。
这个方法是vuex的异步提交数据方法。即获得的数据通过commit一个函数,提交修改状态。
我们知道vuex修改状态的函数只有一个就是mutations函数,同时为了使得函数名同一这里使用的
[types.FETCH_AREA](state, payload) {
state.areas = payload;
},
这里是mutations的定义函数。这里面是将payload参数传递给state。注意之前说过,单一数据流就是要使所有的数据的改变都要通过mutations。这里改变了数据状态。
现在我们看看areas都是什么:
在组件内定义了state
、getter
、actions
、mutations
。state里面存放的就是状态。
const state = {
area: Object.assign({}, StubArea),
areas: [],
areanotes: [],
};
getter层
getAllAreas: stateObj => stateObj.areas,
通过getter方法将获得的信息更新到getter的getAllAreas里面。也就是只要调用getAllAreas就可以获得数据了。
computed: {
...mapGetters({
areas: 'getAllAreas',
通过在计算属性里面使用mapGetter获得属性的映射。
从而对这个属性进行使用
这个就是一整套的流程了
数据通过action映射出去的方法,调用了action,进行了数据的异步请求,返回的数据通过commit调用了mutations函数,更改了数据状态。
一下代码来自某个项目:
// 这里是getter方法
getAssetById: (state, getters) => (id) => {
return getters.getAllAssets.find(asset => {
return asset.id + '' === id + '';
});
},
这里可以看到getter可以通过实现一定的方法使得前端可以通过id等简单标识进行查询,这可以大大减少对后端资源的获取的压力
getAssetTypeById: (state, getters) => (id) => (state.types.find(type => type.id + '' === id + '')),
// ---------------分割线---------------
getAllAssets: (state, getters) => {
let getAssetTypeById = getters.getAssetTypeById;
return state.assets.map(asset => {
let parentAsset = getAssetTypeById(asset.type);
asset.typeObj = parentAsset;
while (parentAsset && parentAsset.parent) {
parentAsset = getAssetTypeById(parentAsset.parent);
}
asset.parentAssetType = parentAsset;
return asset;
});
},
这里是用到的getter方法。没看过徐州项目逻辑,我这里就猜测:有多个设备,这些设备有很多状态,网页上通过点击产生某种事件,使得view层的数据产生了变化。同时对方法解耦,可以复用相关的getter方法,这就是这里实现了对设备状态的切换。可能代码和业务没有理解对,不过这个思路确实很有意思:通过vuex的commit将所有的数据存在前端,在通过getter的方式进行查询,这样可以减少对后端的服务压力。