[MobX State Tree數據組件化開發][0]:開篇

👉系列文章目錄👈

組件化的時代

React、Vue、Angular等庫(框架)出現後,前端進入了UI組件化開發的時代。經過合理地劃分應用的功能,封裝成一個個從底層到高層的組件,最後構造爲一顆組件樹,完成咱們的應用:前端

  • App
    • Page1
      • Component1
      • Component2
      • ...
    • Page2
    • ...

看起來真棒,不是嗎?git

可是在實際開發中,還有一道繞不過去的坎:狀態管理。怎麼組織和劃分應用的狀態,UI組件如何得到自身須要的狀態數據,如何複用狀態部件等等,這些問題困擾了我好久。github

Flux模式

當前的狀態管理方案中,以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

MobX State Tree:數據組件化

相似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),NotificationLoadable以及RemoteResource。其中Notification提供消息通知的功能,Loadable提供了loading狀態以及切換loading狀態的方法,而RemoteResource在前二者的基礎上,提供了加載遠程資源的能力。

三個BaseModel的實現很是簡單,而且與業務邏輯零耦合。最後,經過組合BaseModel並擴展出對應的功能,實現了ProductListFavorites兩個Model。

在構造應用的時候,把應用的功能拆分紅這樣一個個簡單的BaseModel,這樣應用的代碼看起來就會賞心悅目而且更易於維護。

關於本文

本篇文章是「MobX State Tree數據組件化開發」系列文章的開篇,本系列文章將會爲你們介紹MST的使用以及筆者在使用MST的時候總結的一些技巧和經驗。

本系列文章更新週期不肯定,筆者會盡量的抽出時間來編寫後續文章。

喜歡本文的歡迎關注+收藏,轉載請註明出處,謝謝支持。

相關文章
相關標籤/搜索