Flutter 開發常見的 10 個錯誤:如何避免效能與架構問題

Published on: | Last updated:

最近在帶新人,發現很多剛開始寫 Flutter 的朋友,不管有沒有經驗,好像都會卡在幾個很類似的地方。也不是說多難,但就是一些…嗯…觀念上的小坎,跨過去就海闊天空了。想說乾脆把我觀察到的幾個常見的坑整理一下,邊想邊寫,算是一個即時筆記吧。

這些東西不是什麼高深技術,但早點知道,真的可以少走很多冤枉路,也可以讓你的 Code 不會幾個月後連自己都看不懂。

重點一句話

老實說,大部分的坑都不是 Flutter 太難,而是我們用過去寫 Web 或原生 App 的習慣去硬套,結果就水土不服了。核心是搞懂 Widget 生命週期跟怎麼聰明地管理「狀態 (State)」,其他問題大概都是從這兩點延伸出來的。

最常見的幾個坑,還有我會怎麼繞過去

OK,我們一個一個來看。順序不代表重要性,想到什麼就寫什麼。

1. 還沒搞懂 Widget Tree 就在蓋房子

這個絕對是第一名。很多人一開始就把 Widget 當成是 HTML 的 div 或 Android 的 View,覺得就是個方塊。嗯…對,但也不完全對。

Flutter 的一切都是 Widget,排版是 Widget、按鈕是 Widget、連置中對齊都是一個叫 `Center` 的 Widget。它們全部疊在一起,像一棵樹,這就是 Widget Tree。最大的問題在於,很多人不知道這棵樹「何時」以及「如何」會重新長一次。

一個頁面上的狀態(例如計數器的數字)一變,Flutter 不是聰明到只更新那個數字,它可能會把「那一整根樹枝」都砍掉重練。如果你蓋的樹枝太大根,App 就會開始卡,我們叫它 "Jank"。

怎麼做:

  • 分清楚 `StatelessWidget` 跟 `StatefulWidget`:這個是基本功。簡單講,畫面內容不會變的就用 `StatelessWidget`(例如 App 的 Logo、標題列文字)。需要變動的,像是計數器、打勾選項,就用 `StatefulWidget`。
  • 沒事就加 `const`:這超重要。如果你確定一個 Widget 跟它的所有內容都是固定不變的,就在它前面加上 `const`。這等於是跟 Flutter 說:「嘿,這個東西是萬年不變的木頭雕像,下次刷新頁面時你不用理它。」這可以省下超多效能。
  • 打開你的 DevTools:Flutter DevTools 是你的好朋友。裡面有個 "Flutter Inspector",可以直接把你的 Widget Tree 視覺化,你一眼就能看出你的 UI 是怎麼疊的,誰是誰的小孩。

// 不太好的寫法,每次 parent 重建,這個 Text 都會跟著重畫一次
class MyHeader extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text('這是一個不會變的標題');
  }
}

// 好的寫法,加上 const
class MyHeader extends StatelessWidget {
  // 加上 const constructor
  const MyHeader({super.key});

  @override
  Widget build(BuildContext context) {
    // 也告訴 Flutter 裡面的 Text 也是固定的
    return const Text('這是一個不會變的標題');
  }
}
Widget Tree 重建示意圖:只有需要改變的部分會刷新
Widget Tree 重建示意圖:只有需要改變的部分會刷新

2. 狀態管理 (State Management) 選得太隨便

這大概是 Flutter 社群吵最兇的話題了。Provider, Riverpod, Bloc, GetX, MobX... 一大堆。新人看到通常都直接傻眼。

選錯狀態管理方案的後果很嚴重。選了太簡單的,App 變複雜後,你的狀態會像義大利麵一樣到處亂傳,改 A 壞 B。選了太複雜的,一個小功能你可能就要寫一堆樣板程式碼 (boilerplate code),開發速度慢到想哭。

說真的,這個問題沒有標準答案。但有個思路可以參考。

技術選型思路:該用 Provider、Riverpod 還是 BLoC?

這不是採購買東西,但跟選技術棧一樣,要看你的團隊、專案規模和未來性。我自己是這樣看的:

方案 適合情境 我的個人看法(帶點偏見)
Provider 小專案、練手、快速做個 MVP 入門首選,真的很簡單。但它有點「太自由」,專案一大人多,就很容易寫出難以維護的 Code。我自己現在很少在新專案用了。
Riverpod 幾乎所有新專案,從小型到大型 可以看成是 Provider 的官方升級版,同一個作者寫的。它解決了 Provider 的一些痛點,例如不用再綁著 `context`。編譯期就能檢查錯誤,安全很多。我自己是覺得,如果你現在要學,直接學 Riverpod 就好。
BLoC 大型、複雜、需要嚴謹架構的專案 規矩最多,學習曲線最陡。你要寫一堆 Event、State,一開始真的會覺得很煩。但好處是,程式碼結構非常清楚,可預測性超高,非常適合大型團隊協作和寫很完整的單元測試。

重點是,選一個,然後堅持用它。在同一個專案裡混用多種狀態管理,絕對是災難的開始。

3. 把效能優化當成以後的事

「先求有再求好」,這句話在很多地方都對,但在列表頁面完全是個陷阱。

最經典的例子就是用 `Column` 或 `Row` 包著一個用迴圈 `for` 或 `.map` 產生的超長列表。在開發時可能只有 10 筆資料,跑起來順順的。但一上線,資料變成 1000 筆,使用者滑動起來就會像在看 PPT。

原因跟前面說的一樣,`Column` 會一次把所有小孩都畫出來,哪怕它們根本還沒出現在螢幕上。1000 個 Widget 一次渲染,手機 CPU 直接罷工。

怎麼做:

  • 用 `ListView.builder`:這是處理長列表的唯一正解。它很聰明,只會渲染螢幕上「看得到」的那幾個列表項,滑動時再即時回收舊的、創建新的。這叫「懶加載」(lazy loading)。
  • 不要在 `build` 方法裡做重活:`build` 方法應該要超級快,只負責描述 UI。不要在裡面做網路請求、讀寫資料庫這種耗時的操作。
  • 再次拿出 DevTools:裡面有個 Performance 頁籤,可以幫你抓出是哪個畫面、哪個操作導致掉幀 (Frame Drop)。那些紅色的長條就是警告。
用 Flutter DevTools 抓出效能瓶頸 (Jank)
用 Flutter DevTools 抓出效能瓶頸 (Jank)

4. 不寫測試,靠手動點一點就上線

我懂,真的很懂。趕功能都來不及了,哪有時間寫測試。但這就是所謂的「技術債」。你現在省下一小時,未來可能要花一天去除錯。

尤其在用了像 BLoC 這種狀態管理後,你的商業邏輯都封裝在一個個 class 裡,寫單元測試 (Unit Test) 其實非常簡單,CP 值超高。它可以保證你最核心的邏輯是正確的,之後改 UI 或重構時才會有信心。

怎麼做:

  • 從邏輯開始:先不用想著測 UI。先針對你的狀態管理邏輯(例如 BLoC 或 Riverpod 的 Notifier)寫單元測試。保證 `add(1)` 之後狀態真的是 `2`。
  • 再測小組件:用 `WidgetTest` 測試一些獨立的小 Widget,例如一個自訂的按鈕,按下去顏色會不會變。
  • 整合測試是最後的保障:用 `integration_test` 模擬使用者從頭到尾操作一遍 App 的流程,例如登入、瀏覽商品、加入購物車。這個最花時間,但最有價值。

5. 看到什麼酷炫 Package 都想裝

Flutter 的生態系 `pub.dev` 是個寶庫,但也是個陷阱。很多簡單的功能,例如把字串首字母大寫,真的不需要為此裝一個 package。

每多裝一個 package,你的 App 就多了一份依賴、多了一份潛在的 bug 來源、也增加了 App 的體積。有些冷門的 package 作者可能寫一寫就不維護了,結果 Flutter 一更新,你的 App 就因為這個舊 package 而無法編譯,到時候欲哭無淚。

怎麼做:

  • 先問自己:「這個功能我真的不能自己寫嗎?」如果只是幾行程式碼的事,就自己寫吧。
  • 檢查 Package 健康度:在 `pub.dev` 上看它的 Likes、Pub Points 和 Popularity。還要看看它的 GitHub repo 最近有沒有在更新,issue 有沒有人回。
  • 定期大掃除:每隔一段時間就檢查一下 `pubspec.yaml`,把沒在用的 package 刪掉。

其他一些碎碎念的點

還有一些比較小的,但也常常看到:

  • 忽略響應式設計 (RWD):直接寫死 `width: 300`。拜託不要,用 `MediaQuery` 拿螢幕寬度,或用 `Expanded`、`Flexible` 讓 Widget 自己去適應空間。
  • 裸奔的非同步處理:呼叫 API 或讀取檔案,外面都不包 `try-catch`。網路一不穩或檔案不存在,App 就直接閃退給你看。正確處理非同步處理的錯誤是基本素養。
  • 忘記國際化 (i18n):覺得 App 只有台灣人會用,就到處寫死中文字串。等到哪天老闆說想上架日本市場,你就得一個個把寫死的字串找出來,會瘋掉。一開始就用 `flutter_localizations` 把文字抽出來是好習慣。
  • 資安觀念薄弱:這點超嚴重。把使用者 token 或密碼直接存在 `SharedPreferences` 裡。這等於是把鑰匙用透明膠帶貼在家門上。請用 `flutter_secure_storage`,它會把資料存在系統 Keychain 或 Keystore 裡,安全多了。
  • 不跟 Flutter 更新:Flutter 團隊很猛,差不多每季都有大更新。你可以不追最新版,但至少要知道多了什麼新功能、改了什麼東西。常常官方一更新就解決了你之前遇到的效能瓶頸。追蹤官方的 Flutter Blog 是最基本的,但說真的,有些細節還是要看社群討論。像台灣的 Flutter Taiwan 臉書社團 就很活躍,常有人分享踩坑或升級遇到的問題,這種在地化的即時資訊,有時候比硬啃官方文件有用。
技術債的累積:今天圖方便,就是明天要解開的亂麻
技術債的累積:今天圖方便,就是明天要解開的亂麻

說到底,寫程式就是一個不斷踩坑、學習、然後把坑填起來的過程。上面這些點,我自己也幾乎全都踩過一遍。早點知道這些,繞過去,你就能把更多時間花在創造真正有價值的 App 功能上。

不知道你踩過最大的坑是哪個?是狀態管理選錯搞到重構,還是效能沒做好被使用者罵?在下面留言分享一下,看看大家是不是都痛在差不多的地方。互相取暖一下也好。😂

Related to this topic:

Comments

  1. Guest 2025-07-06 Reply
    感覺這份清單有點理想化耶!雖然Flutter很酷,但實際開發時哪有這麼順利呢?我自己在專案中就遇到不少坑,尤其是效能和狀態管理這塊,真的需要不少功夫...
撥打專線 LINE免費通話