模組一|心智模型轉換(Day 1–5)
我是寫了十年以上 Vue 的前端工程師,而且先自首:我不信 Flutter Web。在台灣業界它不流行,我環顧四周也幾乎沒看過用它上線的純 Web 產品。這個系列不是勸你改寫 Flutter,而是一個懷疑者的實測:把 Vue 的心智模型當座標系——reactivity、SFC、路由、狀態管理、建置部署、SEO——逐一對照 Flutter Web,看它到底行不行。
還有一個實驗變數:我不打算從零學 Dart,全程用 AI agent 開發。這不是偷懶,是這個系列的核心假設之一(後面會解釋為什麼 Dart 恰好適合這樣玩)。
2021 年鐵人賽有前輩寫過 Flutter Web(Todd 的〈Flutter web 的奇妙冒險〉),四年過去,HTML renderer 已移除、WASM 路線登場、生態大變——值得用今天的版本重新回答一次。開篇先處理最尖銳的問題:跨平台以外,它還剩什麼優點?
老實盤點。主優點只有一個:mobile 程式碼複用。公司已有 Flutter App,網頁版共用同一份 codebase——這是 Flutter Web 唯一的強理由,其他都是衍生或利基。但利基裡有幾個真實存在,而且有一個很少人談:
1. 渲染一致性:畫布不是 DOM。 CanvasKit/skwasm 直接畫像素,不經 CSS。沒有瀏覽器差異、沒有 cascade 意外、沒有 -webkit- 前綴人生。寫過十年 CSS 的人知道這痛多深。對「UI 必須 100% 符合設計稿」的場景(金融看板、白牌產品)是實質優勢。
2. Canvas 密集型 UI 的天然主場。 繪圖編輯器、互動圖表、遊戲化介面——這類需求在 Vue 你反正也要跳出 DOM 手寫 canvas/WebGL,而 Flutter 的 CustomPaint/RenderObject 管線比裸 canvas API 順手。這是少數「Flutter Web 寫起來比 Vue 順」的領域。
3. 對 AI agent 開發特別有利。 這是本系列的獨家論點,也是我敢不學 Dart 就開工的原因:
flutter analyze、dart format 官方一條龍,agent 自我修正的訊號乾淨4. 單一設計系統強制力。 沒有 CSS 逃生門,沒人能塞 !important。對紀律差的團隊是優點。
5. Web hot reload 已經是預設行為,而且 agent 也能按。 Flutter 3.35 起,hot reload 在 web 上預設啟用(--web-experimental-hot-reload 從此不需要),改完 code 畫面即時更新,路由堆疊、表單輸入、捲動位置都保留;到 3.44,連 --web-hot-reload 這個 flag 都被標記為 deprecated,官方理由是「web 的 hot reload 現在由更現代的機制接手」。開發迴圈與 Vite 的差距已被抹掉一大截,「Flutter Web 開發體驗差」這個舊印象需要重講。更關鍵的是同一版的 Agentic Hot Reload:Dart MCP server 會自動找到並連上正在執行的 Dart/Flutter app,coding agent 不需任何手動設定就能改完 code 直接觸發熱重載——這條直接餵進上面第 3 點,Day 29 會再展開。
台灣不流行不是偶然:SEO 幾乎不能打(內容是 canvas 像素、無 SSR)、首載 bundle 輸 Vue 一個量級、文字選取與右鍵等原生行為都是模擬的、人才池小難交接。這些會在 Day 22、25–28 逐一實測,不含糊帶過。
30 天後的 Day 30,我會拿實測數據回來驗證這棵樹要不要修。
1. 跨平台需求會找上你。 公司若已有 App 團隊用 Flutter,「網頁版能不能直接共用同一份 codebase?」這個問題的評估責任,常常落在前端頭上。不了解它的渲染機制與 SEO 限制,就只能人云亦云;了解了,你才能給出有依據的「可以/不行/有條件可以」。
2. 宣告式 UI 的心智模型是互通的。 UI = f(state) 這件事,Vue template、React JSX、Flutter widget tree 只是三種方言。對照著學,會反過來逼你想清楚 Vue 裡「哪些是宣告式 UI 的本質、哪些只是 Vue 的實作選擇」——例如 virtual DOM 不是必然,細粒度依賴追蹤也不是唯一解。
3. 技術選型需要知道邊界在哪。 Flutter Web 在 bundle size、首屏載入、SEO 上都有結構性的弱點。知道弱點的成因、哪些補救得了、哪些補不了,才能在選型會議上給出可辯護的結論,而不是「聽說它 SEO 不好」。
最關鍵的一件事先講:Flutter Web 預設不產生語意化 DOM。它透過 CanvasKit(Skia 渲染引擎編譯成 WebAssembly)把整個 UI 畫進一個 <canvas>,DOM 裡幾乎只剩一個畫布節點。(早期的 HTML renderer 已在 Flutter 3.29 移除,現在只剩 CanvasKit 與 skwasm 兩種 canvas 路線。)
這一個設計決定,往下衍生出本系列大半的題目:
| 模組 | 天數 | 問題意識 |
|---|---|---|
| 一、心智模型轉換 | Day 1–5 | template、樣式、版面、元件怎麼對照 |
| 二、響應式對照 | Day 6–10 | ref/computed 之後:setState → Provider → Riverpod → Bloc |
| 三、Routing | Day 11–14 | vue-router → go_router,以及 Navigator 2.0 為何複雜 |
| 四、狀態管理 | Day 15–19 | Pinia 對上 Riverpod/Bloc 的設計與選型 |
| 五、建置與部署 | Day 20–24 | Vite vs flutter build web,bundle 與首屏實測 |
| 六、SEO/SSR | Day 25–28 | Nuxt 有的,Flutter Web 拿什麼補 |
| 七、生態系與總結 | Day 29–30 | 套件成熟度、DevTools、完整決策建議 |
每篇固定用同一個結構走:Vue 怎麼做 → Flutter Web 怎麼做 → 差異與坑。
系列裡所有實測數字、code 對照、view-source 比較,都出自同一組對照專案:一個叫 Waypoint Air 的訂票 side project。我用 Vue 和 Flutter Web 各做了一份,畫面刻意做到一模一樣,唯一的變因就是底下那層技術。
兩份都線上可玩,也都可以直接按 F12 對照——這是理解整個系列最快的一步:同一個畫面,左邊是元素,右邊是像素。後面每一天談的優點與代價,都是從這個差別長出來的。
先講清楚邊界:它是我拿來練手的專案,不是上線產品。所以本系列談的是開發體感與架構取捨,不是大流量之下的效能數據——這個限制我會全程守住。
跟完本系列只需要:
flutter devices 能看到 Chrome 即可。明天 Day 2,從最直觀的入口開始:SFC template vs widget tree——同一顆宣告式 UI 的兩種寫法。