在前幾天的 Game List 的例子中,我們消除了 onTap 層層傳遞的問題。但是,如果我們仔細觀察的話,會發現 Game 也是同樣的情況,只要底層的哪個 Widget 需要 Game,中間層的 Widget 就需要當中間人,代為傳遞參數。
https://dartpad.dev/?id=831bf7d1714ef8d70daa0e473d5e2bed
中間的 Widget 傳遞這個參數,自己卻是用不到,這個問題在上一張討論如何傳遞 callback 時也有討論到。今天就來聊聊如何簡化設計。
Flutter 有提供許多基礎類型的 Widget,例如:StatelessWidget, StatefulWidget,而其中有一類 Widget 稱作 InheritedWidget。透過 InheritedWidget,我們能有效地讓子層可以跨過父層,直接存取 InheritedWidget。使用這個特性,我們就能避免層層傳遞 Game 的問題了。
https://dartpad.dev/?id=90fdfc781c4d43360eeb178174918381
經過修改之後,在上面的程式碼中,我們在頂層使用 GameInfo 這個 InheritedWidget,讓 GameInfoSection 和 GameActionSection 自己去取得 GameInfo,最後 GameItemView 也不需要幫忙傳遞 Game 了。
當我們每次想要 Widget Tree 中,縱向的分享資料時,我們都得寫一個 Widget 並讓他繼承 InheritedWidget,但是其實在 Flutter 眾多套件中,有一個能幫助我更簡單的使用 InheritedWidget,那就是 Provider。
https://dartpad.dev/?id=1ec4a9a9cb445e7b50bfb6974628d62d
可以發現使用 Provider 之後的程式碼,簡潔非常多,不需要使用繼承,也不需要特別定義一個新的 Widget,直接使用 Provider 並放入 game 就好。由於 Provider 裏頭其實就是 InheritedWidget,透過 Provider 我們能用組合的方式去使用 InheritedWidget,而不是繼承,想比喻繼承,我們傾向於使用組合,也是一個有名的規則:以組合取代繼承。
值得一提的是,InheritedWidget 是狀態管理的基礎,所以也使得作為 InheritedWidget 的 Wrapper,Provider 也被大量套件引用,其中最知名的就是 Bloc 了。
回想前兩天的主題,按鈕傳遞我們也能把 callback 放在 Provider,當子 Widget 渲染時,從 Provider 身上讀取 callback。但其實這個作法並不太合適,因為當我們從 Provider 讀取 callback 時,也意味著使用這個按鈕必須提供相應的 Provider,即使用他的人不需要按鈕有行為。大多時候,使用 Provider 傳遞狀態會比較合理,因為畫面顯示與資料是一致的,不同的資料會有不同的畫面。相比於資料,按鈕行為就很可能會因為畫面不同,而有不同的功能,所以使用 Provider 來傳遞按鈕事件,就會降低按鈕元件通用性。
InheritedWidget 讓我們可以往子層 Widget 共享資料,避免過多的傳遞參數,畢竟沒有參數的方法或物件最好用。雖然 InheritedWidget 好用,但是在實戰中,我們還是傾向於使用 Provider,畢竟組合還是比繼承要來得有彈性。