React、Vue、Angular等库(框架)出现后,前端进入了UI组件化开发的时代。经过合理地划分应用的功能,封装成一个个从底层到高层的组件,最后构造为一颗组件树,完成咱们的应用:前端
看起来真棒,不是吗?git
可是在实际开发中,还有一道绕不过去的坎:状态管理。怎么组织和划分应用的状态,UI组件如何得到自身须要的状态数据,如何复用状态部件等等,这些问题困扰了我好久。github
当前的状态管理方案中,以Flux模式为主流,表明性的有Redux、Vuex等。redux
举个例子,假如要实现一个电商系统,这个系统中包含“商品列表”、“收藏夹”两个功能,他们都包含一个元素结构类似的商品列表数据,可是数据的来源(接口)不一样。在Redux中,须要这样写:api
// Reducers##
function productList (state = fromJS({loading: false, data: []}), {type, payload}) {
switch (type) {
case types.PRODUCT_GET_LIST_START:
return state.merge({loading: true});
case types.PRODUCT_GET_LIST_SUCCESS:
return state.merge({loading: false, data: payload});
case types.PRODUCT_GET_LIST_FAILURE:
return state.merge({loading: false});
default:
return state;
}
}
function favorites (state = fromJS({loading: false, data: []}), {type, payload}) {
switch (type) {
case types.FAVORITES_GET_START:
return state.merge({loading: true});
case types.FAVORITES_GET_SUCCESS:
return state.merge({loading: false, data: payload});
case types.FAVORITES_GET_FAILURE:
return state.merge({loading: false});
default:
return state;
}
}
// Actions
function getProducts (params) {
return (dispatch, getState) => {
dispatch({type: types.PRODUCT_GET_LIST_START});
return api.getProducts(params)
.then(res => {
dispatch({type: types.PRODUCT_GET_LIST_SUCCESS, payload: res});
})
.catch(err => {
dispatch({type: types.PRODUCT_GET_LIST_FAILURE, payload: err});
});
};
}
function getFavorites (params) {
return (dispatch, getState) => {
dispatch({type: types.FAVORITES_GET_START});
return api.getFavorites(params)
.then(res => {
dispatch({type: types.FAVORITES_GET_SUCCESS, payload: res});
})
.catch(err => {
dispatch({type: types.FAVORITES_GET_FAILURE, payload: err});
});
};
}
export const reducers = combineReducers({
productList,
favorites
});
export const actions = {
getProductList,
getFavorites
};
复制代码
能够看到,一样是商品列表数据的加载,须要写两份几乎相同的reducer和action。难受,很是难受!mvc
看到这,有的朋友可能会说,能够封装成一个工厂方法来生成呀,好比说:app
function creteProductListReducerAndAction (asyncTypes, service, initialState = fromJS({loading: false, data: []})) {
const reducer = (state = initialState, {type, action}) => {
switch (type) {
case asyncTypes.START:
return state.merge({loading: true});
...
}
};
const action = params => dispatch => {
dispatch({type: asyncTypes.START});
return service(params)
.then(res => {
dispatch({type: asyncTypes.SUCCESS, payload: res});
})
.catch(err => {
dispatch({type: asyncTypes.FAILURE, payload: err});
});
}
return {reducer, action};
}
复制代码
乍一看也还能够接受,可是若是有一天,我想要扩展一下favorites的reducer呢?当应用开始变得愈发丰满,须要不断地改造工厂方法才能知足业务的需求。框架
上面的例子比较简单,固然还有更好的方案,社区也有诸如dva的框架产出,可是都不够完美:复用和扩展状态部件很是困难。dom
相似UI组件化,数据组件化很好地解决了Store模式难以复用和扩展的问题。像一个React组件,很容易在组件树的各个位置重复使用。使用HOC等手段,也能方便地对组件自身的功能进行扩展。async
本系列文章的主角:MobX State Tree(后文中简称MST)正是实现数据组件化的利器。
React, but for data.
MST被称为数据管理的React,他创建在MobX的基础之上,吸取了Redux等工具的优势(state序列化、反序列化、时间旅行等,甚至可以直接替换Redux使用,见redux-todomvc example)。
对于MST的具体细节,在开篇中就不赘述了,先来看看如何用MST来编写上文中的“商品列表”和“收藏夹”的数据容器:
import { types, applySnapshot } from 'mobx-state-tree';
// 消息通知 BaseModel
export const Notification = types
.model('Notification')
.views(self => ({
get notification () {
return {
success (msg) {
console.log(msg);
},
error (msg) {
console.error(msg);
}
};
}
}));
// 可加载 BaseModel
export const Loadable = types
.model('Loadable', {
loading: types.optional(types.boolean, false)
})
.actions(self => ({
setLoading (loading: boolean) {
self.loading = loading;
}
}));
// 远端资源 BaseModel
export const RemoteResource = types.compose(Loadable, Notification)
.named('RemoteResource')
.action(self => ({
async fetch (...args) {
self.setLoading(true);
try {
// self.serviceCall为获取数据的接口方法
// 须要在扩展RemoteResource时定义在action
const res = await self.serviceCall(...args);
// self.data用于保存返回的数据
// 须要在扩展RemoteResource时定义在props中
applySnapshot(self.data, res);
} catch (err) {
self.notification.error(err);
}
self.setLoading(false);
}
}));
// 商品Model
export const ProductItem = types.model('ProductItem', {
prodName: types.string,
price: types.number,
...
});
// 商品列表数据Model
export const ProductItemList = RemoteResource
.named('ProductItemList')
.props({
data: types.array(ProductItem),
});
// 商品列表Model
export const ProductList = ProductItemList
.named('ProductList')
.actions(self => ({
serviceCall (params) {
return apis.getProductList(params);
}
}));
// 收藏夹Model
export const Favorites = ProductItemList
.named('Favorites')
.actions(self => ({
serviceCall (params) {
return apis.getFavorites(params);
}
}));
复制代码
一不当心,代码写得比Redux版本还要多了[捂脸],可是仔细看看,上面的代码中封装了一些细粒度的组件,而后经过组合和扩展,几行代码就获得了咱们想要的“商品列表”和“收藏夹”的数据容器。
在MST中,一个“数据组件”被称为“Model”,Model的定义采用了链式调用的方式,而且能重复定义props、views、actions等,MST会在内部将屡次的定义进行合并处理,成为一个新的Model。
再来看上面的实现代码,代码中定义了三个BaseModel(提供基础功能的Model),Notification
、Loadable
以及RemoteResource
。其中Notification
提供消息通知的功能,Loadable
提供了loading状态以及切换loading状态的方法,而RemoteResource
在前二者的基础上,提供了加载远程资源的能力。
三个BaseModel的实现很是简单,而且与业务逻辑零耦合。最后,经过组合BaseModel并扩展出对应的功能,实现了ProductList
与Favorites
两个Model。
在构造应用的时候,把应用的功能拆分红这样一个个简单的BaseModel,这样应用的代码看起来就会赏心悦目而且更易于维护。
本篇文章是“MobX State Tree数据组件化开发”系列文章的开篇,本系列文章将会为你们介绍MST的使用以及笔者在使用MST的时候总结的一些技巧和经验。
本系列文章更新周期不肯定,笔者会尽量的抽出时间来编写后续文章。
喜欢本文的欢迎关注+收藏,转载请注明出处,谢谢支持。