Flutter高內聚元件怎麼做?閒魚打造開源高效方案!

閒魚技術發表於2019-05-23

fish_redux是閒魚技術團隊打造的開源flutter應用開發框架,旨在解決頁面內元件間的高內聚、低耦合問題。開源地址:

從react_redux說起

redux對於前端的同學來說是一個比較熟悉的框架了,fish_redux借鑑了redux單項資料流思想。在flutter上說到redux,大家可能第一反應會類比到react上的react_redux。在react_redux中有個重要的概念——connect

connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])

簡單得說,connect允許使用者從Redux store中獲取資料並繫結到元件的props上,可以dispatch一個action去修改資料。

那麼fish_redux中的connector是做什麼的呢?為什麼說connector解決了元件內聚的問題?我們應該如何理解它的設計呢?

connector in fish_redux

儘管都起到了連線的作用,但fish_reduxreact_redux在抽象層面有很大的不同。

fish_redux本身是一個flutter上的應用框架,建立了自己的component體系,用來解決元件內的高內聚和元件間的低耦合。從connector角度來說,如何解決內聚問題,是設計中的重要考量。

fish_redux自己製造了 Component樹, Component聚合了statedispatch,每一個子 Componentstate透過 connector從父 Componentstate中篩選。如圖所示:

Flutter高內聚元件怎麼做?閒魚打造開源高效方案!

可以看到,fish_reduxconnector的主要作用把父子 Component關聯起來,最重要的操作是filterstate從上之下是一個嚴謹的樹形結構,它的結構複用了 Component的樹形結構。類似一個漏斗形的資料管道,管理資料的分拆與組裝。它表達瞭如何組裝一個 Component

而對於react_redux來說,它主要的作用在於把react框架和redux繫結起來,重點在於如何讓React component具有Redux的功能。

Flutter高內聚元件怎麼做?閒魚打造開源高效方案!

從圖中可以看到,react_reduxReact是平行的結構,經過 mapStateToProps後的state也不存在嚴謹的樹形結構,即對於一個React component來說,它的state來自於Redux store而不是父component的state。從框架設計的角度來說,react_redux最重要的一個操作就是attach

原始碼分析

說完概念,我們從原始碼的角度來看看fish_redux中的connector是如何運作的,以fish_redux提供的example為例

class ToDoListPage extends Page<PageState, Map> {
  ToDoListPage()
      : super(
          ...
          dependencies: Dependencies(
              adapter: ToDoListAdapter(),
              slots: <String, Dependent>{
                'report': ReportConnector() + ReportComponent()
              }),
        ...
        );
}

在ToDoListPage的建構函式中,向父類構造傳遞了一個 Dependencies物件,在構造 Dependencies時,引數 slots中包含了名叫"report"的item,注意這個item的生成,是由一個 ReportConnector+ ReportComponent產生的。

從這裡我們得出一個簡單卻非常重要的結論:

在fish_redux中,一個Dependent = connector + Component 。

Dependent代表頁面拼裝中的一個單元,它可以是一個 Component(透過buildComponent函式產生),也可以是一個 Adapter(由buildAdapter函式產生)。這樣設計的好處是,對於View拼裝操作來說, Dependent對外統一了API而不需要透出更多的細節。

根據上面我們得出的結論, connector用來把一個更小的 Component單元連結到一個更大的 ComponentAdapter上。這與我們之前的描述相符合。

connector到底是什麼?

知道了 connector的基本作用,我們來看一下它到底連結了哪些東西以及如何連結。

先來看一下ReportConnector類的定義:

class ReportConnector extends ConnOp

ReportConnector繼承了 ConnOp類,所有 connector的操作包括+操作,都來自於 ConnOp類。

Flutter高內聚元件怎麼做?閒魚打造開源高效方案!

set/get

既然是資料管道,就會有獲取放置。

set函式的入參很好得表達了 TP的意思,即把一個 P型別的 subState合併到 T型別的 state中。

再回頭看 get函式,就很好理解了, get函式表達的就是如何從 T型別的 state中獲取 P型別的 subStateDependent使用。

operator +

+運算子的過載是我們最初看到connector作用的地方,也是connector發揮作用的入口。

LogicComponentAdapter的父類,它表示頁面組裝元素的邏輯層,裡面包含了 reducer/ effect/ higherEffect等與邏輯相關的元素以及它的組裝過程。

operator +呼叫了 createDependent函式,接著會呼叫到 _Dependent類的建構函式,這裡將 logicconnector放入 _Dependent內部,在後面fish_reduxComponent組裝的過程中,connector會隨著外部對 _Dependent中函式的呼叫發揮作用。

connector正式登場

鋪墊了這麼多,是該connector正式發揮作用的時候了。

get

我們以 Component為例,會呼叫到 _DependentbuildComponent函式

Widget buildComponent(MixedStore store, Get getter) {
    final AbstractComponent component = logic;
    return component.buildComponent(store, () => connector.get(getter()));
}

這裡的 logic實際就是一個 Component物件,在呼叫 ComponentbuildComponent函式的時候,使用 get函式從一個大的父state中獲取到當前 Component需要的資料集。接下去,這個變換後的子state將被用在例如 ViewBuilderRedcuer函式中。

這是connector在資料獲取上的作用。

set

還是在 _Dependent類裡面,看 createSubReducer函式:

SubReducer createSubReducer() {
    final Reducer reducer = logic.reducer;
    return reducer != null ? connector.subReducer(reducer) : null;
}

首現從一個 Logic(這裡實際上是一個 Component)物件中獲取到外部設定進來的 reducer,接著呼叫 subReducer返回一個 SubReducer物件。 SubReducer是一個被wrap後的 Reducer。

subReducer的實現在 MutableConn中, ConnOp繼承了 MutableConn類,也獲得了這個能力

SubReducer subReducer(Reducer reducer) {
    return (T state, Action action, bool isStateCopied) {
      final P props = get(state);
      if (props == null) {
        return state;
      }
      final P newProps = reducer(props, action);
      final bool hasChanged = newProps != props;
      final T copy = (hasChanged && !isStateCopied) 
                  ? _clone(state) : state;
      if (hasChanged) {
        set(copy, newProps);
      }
      return copy;
    };
}

它首現透過 get函式得到一個變換後的資料集 props,接著呼叫原始的 reducer函式進行邏輯處理,這裡有一個最佳化也是 SubReducer的作用,如果資料集在經過 reducer處理之後發生了變化,

並且state已經被copy過一次了(isStateCopied==true),就直接把 newProps透過 set函式更新到state中去。(這個最佳化可以防止多個子state發生變化的時候父state被複製多次)。

至此,connector在資料更新上的作用也體現出來了。

ReportConnector

最後,就好理解ReportConnector的實現了:

  1. class ReportConnector extends ConnOp<PageState, ReportState> {

  2.  @override

  3.  ReportState get(PageState state) {

  4.    final ReportState reportState = ReportState();

  5.    reportState.total = state.toDos.length;

  6.    reportState.done =

  7.        state.toDos.where((ToDoState tds) => tds.isDone).toList().length;

  8.    return reportState;

  9.  }


  10.  @override

  11.  void set(PageState state, ReportState subState) {}

  12. }

很明顯的,在 get函式中, ReportStatePageState中獲得了total/done欄位。

總結

閒魚客戶端的詳情頁完全使用了fish_redux進行了重構,透過高內聚的 Component+connector形式,使得 Component可以被大量複用,很好得支援了5種型別的詳情頁。未來我們會基於fish_redux強大的擴充套件能力製作更多元件來滿足不同業務對於框架的需求。 

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69900359/viewspace-2645351/,如需轉載,請註明出處,否則將追究法律責任。

相關文章