iT邦幫忙

2026 iThome 鐵人賽

DAY 10
0
Modern Web

Vue 演進驗證 × AI Coding 時代的工程實踐系列 第 10

Day 10:AI Coding 如何避免 Reactive Anti-pattern?

  • 分享至 

  • xImage
  •  
Vue 3.6 沒有自動消除 Reactive Chain 的成本,因此 AI 如果持續產生 Reactive Anti-pattern,再新的 Runtime 也救不了。

前一天沒有數據支持 Vue 3.6 沒有減少這條 Reactive Graph 所產生的 Effect / Render 執行次數,這讓我開始思考如果 Reactive Graph 本身就是我們建立出來的,Framework 到底能幫我們多少?

Framework 可以優化 Runtime,但不會替我們重畫 Architecture


我們把 Reactive Update 想成一條路:

Source
  ↓
Computed / Watch
  ↓
Component
  ↓
Browser

如果 Framework 能夠優化 Reactive Runtime,為什麼這條 100 層的 Reactive Chain 仍然維持原本的執行結構?

因為對 Framework 來說,它看到的是一個 合法、而且有明確依賴關係的 Reactive Graph, 每一個節點執行得更有效率,可是…它要怎麼知道其實這條路根本不用蓋這麼長?

Source
 ↓
computed
 ↓
computed
 ↓
watch
 ↓
watchEffect
 ↓
computed
 ↓
Component

過去在 Vue 3.5 和 Vue 3.6 的 Runtime 成本數據沒有出現明顯變化,Computed、Watch、Render 的執行次數也維持一致,所以 我們測到的,會不會不只是 Framework Runtime 的問題,還有 Application 自己建立出來的 Reactive Architecture?

換句話說:

Framework 可以優化「這條路走得多快」,但「這條路為什麼這麼長」,仍然是架構設計的問題。

AI Coding,可能讓這條路變得更長


這件事情放到 AI Coding 時代,就開始變得有趣了,AI 最擅長解決眼前的需求,但是它知道 Reactive Graph 的複雜程度嗎?

以下是跟 AI 合作過程,發現它最容易寫出的程式類型,從這些類型看看它是不是把路走偏了?

1. 為了同步而同步出現的好多 watch

AI 很喜歡用補丁式思考,例如請他幫忙寫一個電商的購物車與折扣結算頁面邏輯,AI 為了處理「原價、折扣、運費、總金額」的連動關係,寫出了三個獨立的 watch

import { ref, watch } from 'vue';

// 原始資料 (Data)
const cartItems = ref([{ id: 1, price: 100, qty: 2 }]);
const couponCode = ref('SAVE10');
const isVIP = ref(true);

// 衍生狀態
const subtotal = ref(0);
const discount = ref(0);
const shipping = ref(60);
const total = ref(0);

// ── 災難開始:AI 針對每個需求獨立寫 watch ──

// 需求 1:購物車變動時,更新商品小計
watch(cartItems, (newItems) => {
  subtotal.value = newItems.reduce((sum, item) => sum + (item.price * item.qty), 0);
  console.log('🔄 watch 1: 小計更新為', subtotal.value);
}, { immediate: true, deep: true });

// 需求 2:當小計或折扣碼、VIP 狀態改變時,更新折扣金額
watch([subtotal, couponCode, isVIP], ([newSub, newCode, newVip]) => {
  let baseDiscount = newCode === 'SAVE10' ? 10 : 0;
  let vipDiscount = newVip ? 20 : 0;
  discount.value = baseDiscount + vipDiscount;
  console.log('🔄 watch 2: 折扣更新為', discount.value);
}, { immediate: true });

// 需求 3:當小計與折扣改變時,計算最終運費與總金額
watch([subtotal, discount], ([newSub, newDisc]) => {
  // 小計滿 500 免運費
  shipping.value = newSub >= 500 ? 0 : 60;
  total.value = newSub - newDisc + shipping.value;
  console.log('🔄 watch 3: 總金額更新為', total.value);
}, { immediate: true });

一個需求一個 watch,看起來很單純,加起來就很可怕,最後...

         cartItems / couponCode / isVIP (Data)
           ┌──────────┼──────────┐
           ▼          ▼          ▼
        watch 1    watch 2    watch 3
           ▼          ▼          ▼
       計算小計 →  計算折扣 →  計算總金額 (Logic)

其實改用 computed 具有快取特性,且會自動並行追蹤依賴

import { ref, computed } from 'vue';

// 原始資料 (Data)
const cartItems = ref([{ id: 1, price: 100, qty: 2 }]);
const couponCode = ref('SAVE10');
const isVIP = ref(true);

// 乾淨、無副作用的派生狀態 (Logic)
const subtotal = computed(() => {
  return cartItems.value.reduce((sum, item) => sum + (item.price * item.qty), 0);
});

const discount = computed(() => {
  let baseDiscount = couponCode.value === 'SAVE10' ? 10 : 0;
  let vipDiscount = isVIP.value ? 20 : 0;

  return baseDiscount + vipDiscount;
});

const shipping = computed(() => subtotal.value >= 500 ? 0 : 60);

const total = computed(() => subtotal.value - discount.value + shipping.value);

2. 把每個中間結果都 Reactive 化

前面說改用 computed ,它又可能走向另一個極端,結果 AI 接受指令遵循「不要用 watch」的指令,像包洋蔥一樣一層包一層,發現另一個常見情況:

user
 ↓
computed
 ↓
computed
 ↓
computed
 ↓
computed

例如:姓名、VIP 稱號、會員等級、畫面標題,每一層都包成 computed,但如果這些只是單純的資料整理,其實沒有必要全部變成 Reactive Boundary。

可以把中間邏輯留在同一個 computed 裡:

const finalTitle = computed(() => {
  const fullName = `${user.lastName}${user.firstName}`
  const vipText = user.vipLevel > 2 ? '[VIP]' : '[一般會員]'

  return `${vipText} ${fullName}`
})

3. 為了同步,又複製一份 State

前面兩個例子都出現 原本只需要一份資料,最後卻建立了更多 Reactive 節點,專案愈做愈大的時候會不知道源頭在哪,於是變成能動的程式碼就不要輕易改變它!

Pinia
  ↓
discountTotal
  ↓
 watch
  ↓
localFinalPrice
  ↓
 UI

一開始看起來沒有問題,但 localFinalPrice 一旦可以被 UI 修改,資料流就變成:

             Pinia
               ↓
         discountTotal
               ↓
             watch
               ↓
        localFinalPrice
               ↕
              UI

這時候,除了 Pinia 還有 UI 也可以改變這個值,也就是當如使用者輸入 750,Pinia 更新後變成 780watch 又會把 780 寫回 localFinalPrice

結果就是你蓋我我蓋你,這不是 Vue Runtime 的問題,而是我們為了「同步」,建立了第二份可以被修改的 State。

為了同步又再複製一份 State

所以 資料已經有明確的 Source,就不要為了方便而複製一份可以被獨立修改的 Reactive State。

比較乾淨的資料流會是:

        Source
          │
          ↓
      Derived State
          │
          ↓
          UI
          │
          ↓
        Action
          │
          └────→ Source

這樣 UI 看到的是 Source 的衍生結果;需要修改時,再透過 Action 回到真正的 Source。

這也是 AI Coding 最需要補上的一層


通常我們大多時候下 Prompt 都是「我要什麼功能」,或是把 PM 需求、Open API 直接複製貼上,這樣很容易遇到上面提到的問題,為了避免最後 Code Review 要另外燒 token,可以先告訴它:

產生 Vue Composition API 程式碼。

請遵循以下原則:

✓ 保持單一資料來源(single source of truth, SSOT)

✓ 優先使用單層 computed,避免建立 computed chain

✓ 僅在必要時使用 watch

✓ 避免建立不必要的響應式狀態(ref、reactive)

✓ 每個 watch 都必須說明存在的目的
【 AI 導向的 Vue 響應式優化工作流 】
  
 🎯 1. AI Prompt (輸入規範)
 ┌───────────────────────────────────────────┐
 │ Checklist 規則注入:                      ─ │
 │  [✓] One source of truth (單一真相)       │
 │  [✗] No computed chains (禁止連續計算)    │
 │  [⚠️] Minimize/Explain watch (減少監聽)   │
 └───────────────────────────────────────────┘
                      │
                      ▼
 🤖 2. Generated Code (程式碼生成)
 ┌───────────────────────────────────────────┐
 │  📄 Vue 3 Component (Composition API)    │
 │     ├── state ──► computed (乾淨的單向流)  │
 │     └── watch ──► [說明文字] (必要才使用)   │
 └───────────────────────────────────────────┘
                      │
                      ▼
 🔍 3. Review (雙重審查機制)
 ┌───────────────────────────────────────────┐
 │  🤖 AI 自檢:是否落實 Explain every watch? │
 │  👥 人工審查:檢查有無進流 Reactive Chain? │
 └───────────────────────────────────────────┘
                      │
              ┌───────┴───────┐
              ▼               ▼
        [ ❌ 失敗 ]       [ ✔️ 成功 ]
        退回修正           合併產出

產生出來的程式碼提交前,也會做最後以下確認再提交 MR。

1. 這個 watch 可以拿掉嗎?
2. 這個 computed 真的需要獨立存在嗎?
3. State Source 只有一個嗎?
4. 有沒有重複 State?
5. Reactive Chain 超過三層了嗎?

結論


這十天的目標,並不是證明 Vue 3.5 或 Vue 3.6 哪一個比較快,而是回答一個更重要的問題:

哪些效能問題可以交給 Framework 解決?哪些問題仍然需要 RD 設計來避免?

到了 AI Coding 時代,AI 可以快速 產生更多 Code,但是沒有好的邊界規範,產生的 Reactive Architecture 極有可能讓未來的自己很想殺掉現在的自己!


上一篇
Day 9:Vue 3.6 Validation - Reactive Chain 的 Runtime 成本真的改善了嗎?
下一篇
Day 11:為什麼專案的 Component 越拆越多?
系列文
Vue 演進驗證 × AI Coding 時代的工程實踐12
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言