💡 背景脈絡與核心思維
在 React 生態中,開發者被嚴格要求遵守「不可變性 (Immutability)」;然而在 Vue 的世界裡,框架的核心設計就是擁抱「直接修改 (Direct Mutation)」。
雖然 Vue 賦予了我們極大的開發便利性,但「框架允許你這麼做」並不代表「架構上你應該到處這麼做」。當我們在處理 外部 Props、全域 Store,甚至是 非同步的迴圈操作 時,毫無節制的直接修改往往會成為專案中難以追蹤的技術債。本文將從 Vue 2 與 Vue 3 的底層差異出發,全面剖析這三大危險邊界。
Vue 為什麼推崇直接修改?因為它在底層幫你做掉了「依賴追蹤」與「畫面更新」的髒活。但 Vue 2 與 Vue 3 在攔截機制上有著本質的區別:
Vue 2 (Object.defineProperty) 的侷限:
Vue 2 透過遞迴劫持物件的 Getter/Setter 來達成響應式。缺點是它無法感知「屬性的新增/刪除」以及「陣列索引的直接賦值」。因此在 Vue 2 時代,如果你直接寫 this.obj.newMember = 'A' 或 this.arr[0] = 'B',畫面是不會動的,你必須被迫使用 this.$set。這種底層限制,反而變相阻止了某些過度隨意的 Mutate 行為。
Vue 3 (Proxy) 的全面解放:
Vue 3 改用原生的 Proxy 來代理整個物件,無論你是新增屬性、修改深層結構,甚至是操作陣列索引,Proxy 都能完美攔截。這極大地提升了開發體驗,但也意味著開發者可以毫無阻礙地在任何地方修改深層屬性,這為後續的架構維護埋下了隱患。
詳細閱讀:📘 Vue2 與 Vue3 底層差異:defineProperty 與 原生的 Proxy
這是許多資深開發者也會踩中的地雷。雖然 computed 本身是唯讀的(除非定義了 Setter),但當它回傳一個物件或陣列時,JavaScript 的參照特性會繞過 Vue 的唯讀保護。
當 Computed 根據某些狀態回傳一個全新物件:
// Vue 3 寫法示範
const price = ref(100);
const qty = ref(2);
const orderInfo = computed(() => {
return {
total: price.value * qty.value,
discount: 0
};
});
如果你執行 orderInfo.value.discount = 50:
state.price 發生變動,Computed 就會重新計算並回傳一個全新的物件。你剛剛手動改的 50 會無預警地歸零。這種「狀態遺失」是 UI 閃爍 Bug 的頭號來源。當 Computed 直接回傳一個現有的響應式物件:
// 將全域 Store 的物件透過 computed 曝露給 Template 用
const userStore = useUserStore();
const currentUser = computed(() => userStore.profile);
執行 currentUser.value.name = 'Parker':