認識 Flutter 一個月後,談談自己從 Web 開發到 Flutter 的學習痛點
在 2019 年 5 月 Google I/O 大會上 Google 發表了跨平台開發框架 Flutter,Google 在 2005 年收購 Skia 引擎,經過 10 幾年開發,在 2017 釋出了初版的 Flutter 版本,而目前的穩定版本為 1.7.8,最新的版本目前已可支援
行動版: Android, iOS
桌面版: Windows, macOS, Linux (Desktop Embedding for Flutter)
另外,原生平台推出 Kotlin 語言 Multiplatform Projects 企圖統合死對頭 iOS ,2019 年可以說是 Google 野心計畫起跑。
Google 想要用 Flutter 來一統江山,這 2019 年爆紅的技術當然要來暸解一下啦。
陌生的 Dart 語言
首先,我本身壓根兒沒看過 Dart 語言,與它是第一次見面。剛學習時,Dart 的有些奇特語法是我以前接觸過的語言裡沒見過的,折騰了不少時間,例如
value ??= 'default'; // value == null 時,設為 'default'
final text = value ?? 'never'; // value != null ? value : nullfinal String wtf = someArray
..clear()
..addAll(['W', 'T', 'F'])
..join(''); // 類似 jQuery 的鏈式語法
整體寫下來 Flutter + Dart 與 React + Typescript 其實有 87 分像,一些相似的語法,可以參考這篇文章 [Flutter for JavaScript Developers]
Dart 與 React 都有著 State 管理的概念,而 Type 檢測比 Typescript 更嚴謹,有學過 Vue 或 React 這種 State 管理框架的人,接觸 Flutter 時會發現有很多相似之處,感覺很適合接觸學習來添加一項技能。
Flutter 函式庫內上百個小組件 (Widget)
在練習的過程中,試著把以前開發過的行動版 Web 頁面,用 Flutter 來拉出相同的佈局及功能,拉著拉著…問題就來了,什麼場景要用什麼 Widget 這真的是傷透腦筋。
在 Flutter 函式庫裡,已經針對各種不同的場景與情境,定義了很多的 Widget ,要能活用這些 Widget 就只有寫過看過才能記在腦海中。不過 Google 官方很佛心的,開發了一個範例 App (Flutter Gallery) 並且有上架到 Google Play 也有 Web 版,另外有開放 Github 原始碼,裡面幾乎把所有的組件都展示了,可以直接看源碼來學,省了不少翻文件的時間。
Flutter 裡 Widget 也是 Class ,有著 OOP 的設計,越強大的 Widget 其繼承的鏈就越長,消耗的內存資源也就更多,例如 Container 這個 Widget 非常汎用,汎用性就有如 HTML 的 div 一樣,但有些場景只是要加入 Padding 而已,那麼其實只需要使用 Padding 這個 Widget 即可,不過就是這點常常讓我傷透腦筋,當下很難馬上想到要用哪個 Widget ,但摸久了這問題就漸漸消失了。
佈局、樣式、邏輯都混成一坨
原本設計一個 Web 頁面,可以先把 HTML 的樣板刻好,定義階層 ClassName ,樣式集中在 CSS 即可,對應邏輯的狀態變化也可以透過切換 ClassName 來達到效果,佈局與樣式可以理所當然的分開撰寫。
而當使用 Flutter 拉好一個完整頁面之後,因為 Widget 會與邏輯及樣式綁得死死的,如果沒有好好的切分 Widget 的話,會發現槽狀的深度,快跟馬里亞納海溝一樣深了(─.─||),這對於程式維護性來說實在是很糟。
好在 Flutter 的 Widget 的設計就像樂高積木一樣,每個小組件可以組裝成一個大部件,大部件再組裝回主體;而帶有 State 的組件,也可以透過 InheritedWidget 或 Provider 的方式封裝,我覺得 Flutter 在 State 傳遞或共享這方面的設計很棒。
JSON 資料的轉換
這問題只要是剛接觸 Dart 一定會遭遇的問題,任何透過 API 或是載入 JSON 檔案,都要處理這煩人的問題。
在 Dart 裡每個 Type 都有執行實體,雖然可以像 TS 一樣定義成 dynamic 任意型別 (如同 Typescript 的 any),不過只要在 Runtime 時沒有轉換成對應的型別,就會產生 Runtime error ,被建立 JSON 內容基本上都是屬於 dynamic 型別,當你需要讀取 JSON Object 內的值時,你必須先將之實體化,這意味著任何結構性的 Object 都需要有個 Class 來建構實體。
在上面的程式碼中,如果少了 Line:15–17 那幾行的處理的話,Dart 就會擲出 instance 的錯誤,這個限制使你一定要將資料整理成各個 Model ,就像 Typescript 定義一堆 Interface ,不過卻外加了實作,累~。前端透過 API 向後端資料抓取 JSON 格式的資料時,每種資料都必須建立一種 Model。
Animation 的實作概念轉換
在 Web 上如果要讓一張圖持續的旋轉,用 CSS 很簡單就能做到,只要定義好 Keyframe,接著宣告 class 掛上 animation 參數,再把 class 掛到元素上,監聽 animationstart 及 animationend 來控制,基本就完成了。
Flutter 就沒這麼簡單了,但了解之後其實概念相當類似。在 Flutter 裡, CSS 的 Keyframe 就像是 Flutter 各式各樣的 Tween 類別,來代表 Animation 的數值如何變化;Animation 數值變化時,都會觸發渲染,可以根據 Animation 數值的變化來呈現動畫效果,Animation 也會有 status 變化來得知動畫起始或結束;而 CSS 的 class 的切換就等同於 Flutter 的 AnimationController 來控制動畫開始及結束。
後談
Flutter 藉由 Skia 渲染引擎直接使用 C/C++調用原生的 API ,成功的達到高校能的渲染效果,因此在性能評價中贏過了 React Native 。而 flutter_web 目前尚未正式 Release 雖然只有預覽版,但功能是讓人相當驚艷。
另外,早在 2 年前,skia-wasm-port 已經將 Skia 引擎使用 Webassembly 將 HTML5 原本的 Canvas 渲染引擎替換掉,使用 Skia 引擎來達到更高效能的渲染 (Demo) ,而官方也聲稱他們有計劃規劃 3D API ,這意味的未來 Flutter 或許也能開發 3D 遊戲。
我最害怕的還是有關於原生插件的部分,Flutter 原生插件的生態系與 Cordova 相當類似,雖然官方提供了很多插件,但無法包含所有需求,因此有些插件是第三方開發者自行開發然後發佈到 Flutter pub.dev 上,品質參差不齊,雖然每個插件有著評分機制,當插件已沒有在維護時,分數就會很低,但不免還是令人擔心。
總之。經過兩個禮拜多的摸索,我寫了踩地雷、井字遊戲、俄羅斯方塊來做練習,如果有興趣看看我的程式屍體 (Flutter for web demo),歡迎唾棄及吐槽 。有學過 React, Vue 這種 State 管理框架的人,推薦可以去學習 Flutter 看看。