刚刚,阿里宣布开源Flutter应用框架Fish Redux!
3月5日,閑魚宣布在GitHub上開源Fish Redux,Fish Redux是一個基于 Redux 數據管理的組裝式 flutter 應用框架, 特別適用于構建中大型的復雜應用,它最顯著的特征是 函數式的編程模型、可預測的狀態管理、可插拔的組件體系、最佳的性能表現。下文中,我們將詳細介紹Fish Redux的特點和使用過程,以下內容來自InfoQ獨家對閑魚Flutter團隊的采訪和Fish Redux的開源文檔。
開源背景
在閑魚接入Flutter之初,由于我們的落地的方案希望是從最復雜的幾個主鏈路進行嘗試來驗證flutter完備性的,而我們的詳情整體來講業務比較復雜,主要體現在兩個方面:
- 頁面需要集中狀態管理,也就是說頁面的不同組件共享一個數據來源,數據來源變化需要通知頁面所有組件。
- 頁面的UI展現形式比較多(如普通詳情、閑魚幣詳情、社區詳情、拍賣詳情等),工作量大,所以UI組件需要盡可能復用,也就是說需要比較好的進行組件化切分。
在我們嘗試使用市面上已有的框架(google提供的redux以及bloc)的時候發現,沒有任何一個框架可以既解決集中狀態管理,又能解決UI的組件化的,因為本身這兩個問題有一定的矛盾性(集中vs分治)。因此我們希望有一套框架能解決我們的問題,fish redux應運而生。
fish redux本身是經過比較多次的迭代的,目前大家看到的版本經過了3次比較大的迭代,實際上也是經過了團隊比較多的討論和思考。
第一個版本是基于社區內的flutter_redux進行的改造,核心是提供了UI代碼的組件化,當然問題也非常明顯,針對復雜的詳情和發布業務,往往業務邏輯很多,無法做到邏輯代碼的組件化。
第二個版本針對第一個版本的問題,做出了比較重大的修改,解決了UI代碼和邏輯代碼的分治問題,但同時,按照redux的標準,打破了redux的原則,對于精益求精的閑魚團隊來講,不能接受;
因此,在第三個版本進行重構時,我們確立了整體的架構原則與分層要求,一方面按照reduxjs的代碼進行了flutter側的redux實現,將redux的原則完整保留下來。另一方面針對組件化的問題,提供了redux之上的component的封裝,并創新的通過這一層的架構設計提供了業務代碼分治的能力。
至此,我們完成了fish redux的基本設計,但在后續的應用中,發現了業務組裝以后的代碼性能問題,針對該問題,我們再次提供了對應的adapter能力,保障了在長列表場景下的big cell問題。目前,fish redux已經在線上穩定運行超過3個月以上,未來,期待fish redux給社區帶來更多的輸入。
Fish Redux技術解析
分層架構圖
架構圖:主體自底而上,分兩層,每一層用來解決不通層面的問題和矛盾,下面依次來展開。
Redux
Redux 是來自前端社區的一個數據管理框架,對 Native開發同學來說可能會有一點陌生,我們做一個簡單的介紹。
Redux 是做什么的?
Redux 是一個用來做可預測易調試的數據管理的框架。所有對數據的增刪改查等操作都由 Redux 來集中負責。
Redux 是怎么設計和實現的?
Redux 是一個函數式的數據管理的框架。傳統 OOP 做數據管理,往往是定義一些 Bean,每一個 Bean 對外暴露一些 Public-API 用來操作內部數據(充血模型)。
函數式的做法是更上一個抽象的緯度,對數據的定義是一些 Struct(貧血模型),而操作數據的方法都統一到具有相同函數簽名 (T, Action) => T 的 Reducer 中。
FP:Struct(貧血模型) + Reducer = OOP:Bean(充血模型)
同時 Redux 加上了 FP 中常用的 Middleware(AOP) 模式和 Subscribe 機制,給框架帶了極高的靈活性和擴展性。
貧血模型、充血模型請參考:
https://en.wikipedia.org/wiki/Plain_old_Java_object
Redux 的缺點
Redux 核心僅僅關心數據管理,不關心具體什么場景來使用它,這是它的優點同時也是它的缺點。
在我們實際使用 Redux 中面臨兩個具體問題:
- Redux 的集中和 Component 的分治之間的矛盾;
- Redux 的 Reducer 需要一層層手動組裝,帶來的繁瑣性和易錯性。
Fish Redux 的改良
Fish Redux 通過 Redux 做集中化的可觀察的數據管理。然不僅于此,對于傳統 Redux 在使用層面上的缺點,在面向端側 flutter 頁面緯度開發的場景中,我們通過更好更高的抽象,做了改良。
一個組件需要定義一個數據(Struct)和一個 Reducer。同時組件之間存在著父依賴子的關系。通過這層依賴關系,
我們解決了【集中】和【分治】之間的矛盾,同時對 Reducer 的手動層層 Combine 變成由框架自動完成,大大簡化了使用 Redux 的困難。
我們得到了理想的集中的效果和分治的代碼。
對社區標準的 follow
State、Action、Reducer、Store、Middleware 以上概念和社區的 ReduxJS 是完全一致的。我們將原汁原味地保留所有的 Redux 的優勢。
如果想對 Redux 有更近一步的理解,請參考:https://github.com/reduxjs/redux
Component
組件是對局部的展示和功能的封裝。 基于 Redux 的原則,我們對功能細分為修改數據的功能(Reducer)和非修改數據的功能(副作用 Effect)。
于是我們得到了,View、 Effect、Reducer 三部分,稱之為組件的三要素,分別負責了組件的展示、非修改數據的行為、修改數據的行為。
這是一種面向當下,也面向未來的拆分。在面向當下的 Redux 看來,是數據管理和其他。在面向未來的 UI-Automation 看來是 UI 表達和其他。
UI 的表達對程序員而言即將進入黑盒時代,研發工程師們會把更多的精力放在非修改數據的行為、修改數據的行為上。
組件是對視圖的分治,也是對數據的分治。通過逐層分治,我們將復雜的頁面和數據切分為相互獨立的小模塊。這將利于團隊內的協作開發。
關于 View
View 僅僅是一個函數簽名: (T,Dispatch,ViewService) => Widget
它主要包含三方面的信息
- 視圖是完全由數據驅動。
- 視圖產生的事件/回調,通過 Dispatch 發出“意圖”,不做具體的實現。
- 需要用到的組件依賴等,通過 ViewService 標準化調用。比如一個典型的符合 View 簽名的函數。
關于 Effect
Effect 是對非修改數據行為的標準定義,它是一個函數簽名: (Context, Action) => Object
它主要包含四方面的信息
- 接收來自 View 的“意圖”,也包括對應的生命周期的回調,然后做出具體的執行。
- 它的處理可能是一個異步函數,數據可能在過程中被修改,所以我們不崇尚持有數據,而通過上下文來獲取最新數據。
- 它不修改數據, 如果修要,應該發一個 Action 到 Reducer 里去處理。
- 它的返回值僅限于 bool or Future, 對應支持同步函數和協程的處理流程。
比如良好的協程的支持:
關于 Reducer
Reducer 是一個完全符合 Redux 規范的函數簽名:(T,Action) => T
一些符合簽名的 Reducer:
同時我們以顯式配置的方式來完成大組件所依賴的小組件、適配器的注冊,這份依賴配置稱之為 Dependencies。
所以有這樣的公式 Component = View + Effect(可選) + Reducer(可選) + Dependencies(可選)。
一個典型的組裝:
通過 Component 的抽象,我們得到了完整的分治,多緯度的復用,更好的解耦。
Adapter
Adapter 也是對局部的展示和功能的封裝。它為 ListView 高性能場景而生,它是 Component 實現上的一種變化。
它的目標是解決 Component 模型在 flutter-ListView 的場景下的 3 個問題:
1)將一個"Big-Cell"放在 Component 里,無法享受 ListView 代碼的性能優化;
2)Component 無法區分 appear|disappear 和 init|dispose ;
3)Effect 的生命周期和 View 的耦合,在 ListView 的場景下不符合直觀的預期。
概括的講,我們想要一個邏輯上的 ScrollView,性能上的 ListView ,這樣的一種局部展示和功能封裝的抽象。做出這樣獨立一層的抽象是我們看實際的效果,我們對頁面不使用框架Component,使用框架 Component+Adapter 的性能基線對比。
- Reducer?is?long-lived,?Effect?is?medium-lived,?View?is?short-lived.
我們通過不斷的測試做對比,以某 Android機為例:
- 使用框架前 我們的詳情頁面的 FPS,基線在 52FPS;
- 使用框架, 僅使用 Component 抽象下,FPS 下降到 40, 遭遇“Big-Cell”的陷阱;
- 使用框架,同時使用 Adapter 抽象后,FPS 提升到 53,回到基線以上,有小幅度的提升。
Directory
推薦的目錄結構會是這樣
sample_page -- action.dart -- page.dart -- view.dart -- effect.dart -- reducer.dart -- state.dart components sample_component -- action.dart -- component.dart -- view.dart -- effect.dart -- reducer.dart -- state.dart上層負責組裝,下層負責實現, ? 同時會有一個插件提供, 便于我們快速填寫。
以閑魚的詳情場景為例的組裝:
組件和組件之間,組件和容器之間都完全的獨立。
Communication Mechanism
- 組件|適配器內通信
- 組件|適配器間內通信
簡單的描述:采用的是帶有一段優先處理的廣播, self-first-broadcast。
發出的 Action,自己優先處理,否則廣播給其他組件和 Redux 處理。最終我們通過一個簡單而直觀的 dispatch 完成了組件內,組件間(父到子,子到父,兄弟間等)的所有的通信訴求。
Refresh Mechanism
數據刷新
- 局部數據修改,自動層層觸發上層數據的淺拷貝,對上層業務代碼是透明的。
-
層層的數據的拷貝:
- 一方面是對 Redux 數據修改的嚴格的 follow。
- 另一方面也是對數據驅動展示的嚴格的 follow。
視圖刷新
扁平化通知到所有組件,組件通過 shouldUpdate 確定自己是否需要刷新。
Fish Redux的優點
數據的集中管理
通過 Redux 做集中化的可觀察的數據管理。我們將原汁原味地保留所有的 Redux 的優勢,同時在 Reducer 的合并上,變成由框架代理自動完成,大大簡化了使用 Redux 的繁瑣度。
組件的分治管理
組件既是對視圖的分治,也是對數據的分治。通過逐層分治,我們將復雜的頁面和數據切分為相互獨立的小模塊。這將利于團隊內的協作開發。
View、Reducer、Effect 隔離
將組件拆分成三個無狀態的互不依賴的函數。因為是無狀態的函數,它更易于編寫、調試、測試、維護。同時它帶來了更多的組合、復用和創新的可能。
聲明式配置組裝
組件、適配器通過自由的聲明式配置組裝來完成。包括它的 View、Reducer、Effect 以及它所依賴的子項。
良好的擴展性
核心框架保持自己的核心的三層關注點,不做核心關注點以外的事情,同時對上層保持了靈活的擴展性。
-
框架甚至沒有任何的一行的打印的代碼,但我們可通過標準的 Middleware 來觀察到數據的流動,組件的變化。
- 在框架的核心三層外,也可以通過 dart 的語言特性 為 Component 或者 Adapter 添加 mixin,來靈活的組合式地增強他們的上層使用上的定制和能力。
- 框架和其他中間件的打通,諸如自動曝光、高可用等,各中間件和框架之間都是透明的,由上層自由組裝。
精小、簡單、完備
- 它非常小,僅僅包含 1000 多行代碼;
- 它使用簡單,完成幾個小的函數,完成組裝,即可運行;
- 它是完備的。
關于未來
開源之后,閑魚打算通過以下方式來維護Fish Redux:
- 通過后續的一系列的對外宣傳,吸引更多的開發者加入或者使用。目前Flutter生態里,應用框架還是空白,有機會成為事實標準;
- 配合后續的一系列的閑魚Flutter移動中間件矩陣做開源;
- 進一步提供,一系列的配套的開發輔助調試工具,提升上層Flutter開發效率和體驗。
Fish Redux 目前已在阿里巴巴閑魚技術團隊內多場景,深入應用。最后 Talk is cheap, Show me the code,我們今天正式在GitHub上開源,更多內容,請到GitHub了解。
原文鏈接
本文為云棲社區原創內容,未經允許不得轉載。
總結
以上是生活随笔為你收集整理的刚刚,阿里宣布开源Flutter应用框架Fish Redux!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 阿里云全球首推流量型独享虚拟主机新规格,
- 下一篇: 这个情人节,工程师用阿里云来试着表达不一