- 原文地址:What even are Flutter widgets?
- 原文作者:Matt Carroll
- 譯文出自:掘金翻譯計劃
- 本文永久連結:github.com/xitu/gold-m…
- 譯者:ALVIN
- 校對者:DateBro
Flutter 元件到底是什麼?
以下對 Flutter 元件的解釋是我的個人觀點,並不是 Flutter 框架的元件或相關領域的官方解釋。想要了解有關 Flutter 團隊對其看法,請參閱 Flutter 的官方文件。
Flutter 是一個移動端的 UI 框架,它使 UI 開發變得更有趣、快速和簡單。但從傳統的 Android 和 iOS 到 Flutter,感覺真的不可思議。之所以感到驚訝是因為我們從可變的、生命週期長的 View
和 UIView
,變成了這些不可改變的、生命週期短的 Widget
。它們到底是什麼呢,它們又為什麼高效呢?
最近發表了一篇關於 Widget 與 Element 和 RenderObjects 的關係的文章。我非常推薦這篇文章,建議你繼續深入研究,直到你可以完全理解其內容為止。但對於那些已經迷失在 Widget
的人,請允許我提供一些可能有幫助的解釋。
傳統的 View 和檢視模型
我一直支援在移動 UI 開發中使用檢視模型。
無論你是在 Android 還是 iOS 上工作,都要考慮自定義的 View
或 UIView
,稱為ListItemView
。這個 ListItemView
在左邊顯示一個圖示,然後在圖示右邊顯示字幕上方的標題,最後在右側顯示一個可選附件:
在定義這個自定義 View
的時候,你可以將每個對 View 的描述設為獨立屬性:
myListItemView.icon = blah;
myListItemView.title = “blah”;
myListItemView.subtitle = “blah”;
myListItemView.accessory = blah;
複製程式碼
從技術上來說,這沒什麼問題,但是帶來了架構成本。通過獨立定義每個配置,你對其描述的 Object
需要引用你的 View
,以便它可以配置每個屬性。但是,如果你使用檢視模型,那麼你的描述 Object
可以在不引用 View
的情況下執行,這意味著描述 Object
可以進行單元測試,並且它避免了對具體 View
的編譯時依賴性:
class ListItem {
final Icon icon;
final String title;
final String subtitle;
final Icon accessory;
...
}
// 使用 Presenter 建立一個新的檢視模型。
myListItem = myPresenter.present();
// 傳遞檢視模型到 View 來渲染新的檢視外觀。
myListItemView.update(myListItem);
複製程式碼
這種使用檢視模型的基本原理,與 Flutter 無關,但與傳統的 View
相比,理解檢視模型是非常重要的。檢視模型是一個不可變的配置,需要應用於生命週期長的,可變的 View
。
傳統 Android 和 iOS 中的依賴關係如下:
MyAndroidView -> MyAndroidViewModel
MyiOSUIView -> MyiOSViewModel
複製程式碼
換句話說,在傳統的 Android 和 iOS 中,我們主要使用可變的,生命週期長的 View
(和 UIView
)。我們通過使用那些生命週期長的 View
(和UIView
)Object
來定義佈局 XML,Storyboard 和可程式設計的佈局。然後,我們不定期會傳遞新的檢視模型來改變它們的介面。
現在,讓我們來談談 Flutter。
Flutter 顛覆了這種依賴關係
與其使用可變的、生命週期長,且會不定期接收新的檢視模型的 View
,不如我們只使用不可變檢視模型,來配置可變的、生命週期長的 View
?
以前是:
MyView -> MyViewModel
複製程式碼
現在改為:
MyViewModel -> MyView
複製程式碼
就像這樣,簡單來說,我們剛剛發明了 Flutter 的元件系統:
MyWidget -> MyElement
MyWidget -> MyRenderObject
複製程式碼
這些元件都是不可變的,其中包含了許多用於配置渲染內容方式的屬性:
// 這個元件看起來肯定很像一個檢視模型,不是嗎?
new Container(
width: 50.0,
height: 50.0,
padding: EdgeInsets.all(16.0),
color: Colors.black,
);
// Flutter 元件和傳統檢視有一個很大的區別
// 就是這些元件同樣可以
// 例項化生命週期長的檢視
final mutableSubtree = myContainer.createElement();
final mutableRender = myContainer.createRenderObject();
複製程式碼
但為什麼這些元件能建立這兩樣東西呢?我認為,元件應該只能建立一個生命週期長的檢視?
在 Flutter 中,父/子的概念獨立於渲染而存在。在 iOS 和 Android 中,父/子關係與繪製到螢幕的概念是一致的。
例如,在 Android 中,ViewGroup 需要負責:
// 父/子關係
myViewGroup.addView(...);
myViewGroup.removeView(...);
// 以及
// 佈局和繪製
myViewGroup.measure(...);
myViewGroup.layout(...);
myViewGroup.draw(...);
複製程式碼
在 Flutter,我們有
// Element 來管理父/子關係
myElement.mount(); // 這建立並新增子級元件
myElement.forgetChild(...); // 移除子級元件
// 用 RenderObjects 來佈局和繪製:
myRenderObject.layout();
myRenderObject.paint();
複製程式碼
所以說,儘管 Flutter 中的元件負責建立一個 Element
和一個 RenderObject
,但這兩個 Object
的組合等同於 Android 單個 ViewGroup
相同的功能。
因此,在 Flutter 中,我們使用可以配置 View
的檢視模型,而不是使用檢視模型配置的 View
。這種關係是顛倒的。
為什麼說這種顛倒關係是個大問題
顛倒檢視模型的關係,以及如果一個檢視模型知道如何例項化相對應的一個長生命週期的檢視,你可能會感到特別奇怪。但 Flutter 向我們展示的是,通過顛倒這種關係,我們實現了以宣告方式組合使用者介面的能力。
在我看來,Flutter 正在做的事情,從根本上說,像是正在接近繪製畫素的特定領域語言。
特定領域的語言是幾乎所有開發人員的終極目標。如果你正在為航空業開發應用,那麼你將花費大量時間構建行業特定術語的實現,如航班清單、登機牌、座位分配和會員身份。你在利用較低階別的語言語義,來表示這些術語在你的行業中的含義。然而,理想情況下,在某些時候,開發人員將停止使用這種較低階別的構造方式,他們將開始使用像 FlightManifest
、BoardingPass
和 SeatAssignment
這樣的 Object
來實現整個應用。
但並非每個問題都是商業問題。一些問題是技術問題,例如渲染。渲染使用者介面本身就是一個問題範疇。Flutter 正通過設計出用於渲染使用者介面的一種特定領域的語言來解決此問題。就像 SQL 是用於搜尋資訊宣告式的領域特定語言一樣,Flutter 的元件系統正在成為用於組合使用者介面的宣告式的領域特定語言。這可以通過在外部放置不可變檢視模型,同時將可變檢視限制在內部來實現。
希望這個視角,可以幫助你理解和欣賞 Flutter 中的元件。但是如果沒有,只要你繼續使用 Flutter 的 API,最終你也會體驗出箇中的妙處。
如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。
掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。