iT邦幫忙

2026 iThome 鐵人賽

DAY 12
0
Modern Web

現代函式庫與JavaScript的關係系列 第 12

Day 12 | 響應式與不可變的碰撞:從狀態機制看 useState(React vs Vue 3 vs Angular)

  • 分享至 

  • xImage
  •  

在現代前端開發中,「狀態(State)」是驅動 UI 變更的核心。當狀態改變時,框架如何得知?又是如何決定哪一部份的 DOM 需要更新?

React 的 useState 採用了 不可變資料(Immutability)與重繪(Re-render) 的哲学;而 Vue 3 與 Angular 則走出了完全不同的技術路線。

本文將從 狀態更新機制、記憶體追蹤(Memory Tracking)與 UI 變更偵測(Change Detection) 的角度,深入解析三者的實踐與底層邏輯。


一、 React:useState 的「不可變性」與「重新執行」

要理解 React 的 useState,必須掌握兩個關鍵概念:不可變資料(Immutable Data)快照(Snapshot)

1. 運作機制:重新呼叫與語境快照

在 React 中,狀態變更不會直接修改原本的變數,而是透過useState解構出來的 Setter 函式(即範例中的 setCount)傳入一個「全新」的值,藉此通知 React 觸發 Re-render。

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    // ❌ 直接修改無效:count = count + 1;
    // ✅ 必須透過 Setter 觸發 Re-render
    setCount(count + 1);
  };

  return <button onClick={handleClick}>Count: {count}</button>;
}

  • Setter函式的作用:setCount是React暴露給開發者的更新管道。呼叫setCount(newValue)時,React會把newValue存入內部對應的Fiber節點中。並向排成器登記:「這個組件需要重新渲染」。
    • 記憶體視角:每一次渲染時,count 都是一個獨立的 常數(Primitive Value/Snapshot)。呼叫 setCount 並不會「原地修改」count 的記憶體內容,而是告訴 React:「請用新的狀態值,重新執行一次 Counter() 函式」。
  • Diffing & Reconciliation:React 重新執行函式取得新的 Virtual DOM,並透過 Diff 演算子比對新舊 Virtual DOM 的差異,最後才精準套用到真實 DOM 上。

二、 Vue 3:ref / reactive 的「響應式追蹤」

與 React 要求 Immutable 不同,Vue 3 的核心哲學是 可變資料(Mutable Data)與響應式依賴追蹤(Reactivity System)

1. 運作機制:Proxy 與依賴收集 (Dependency Tracking)

Vue 3 在 <script setup> 中定義狀態,會使用 ref()reactive()

<script setup>
import { ref } from 'vue'

// 1. 建立一個響應式物件
const count = ref(0)

const handleClick = () => {
  // ✅ 直接修改 .value 屬性(Mutable Write)
  count.value++
}
</script>

<template>
  <button @click="handleClick">Count: {{ count }}</button>
</template>

  • 記憶體視角ref(0) 在 Heap 記憶體中建立了一個含有 .value getter/setter 的包裝物件(內部透過 Proxy 或 Property Accessor 實作)。
  • 無須全函式重跑<script setup> 區塊只會執行一次
  • 精準更新(Fine-grained Reactivity)
  1. Getter (Track):當 Template 讀取 count.value 時,Vue 的響應式系統自動將該 DOM 節點記錄為「依賴者(Subscriber)」。
  2. Setter (Trigger):當執行 count.value++ 時,觸發 Setter,Vue 精準地更新與該狀態綁定的 DOM,不需要進行整個組件樹的 Virtual DOM 大面積比對。

三、 Angular:Signals 與傳統 Zone.js 的雙軌實踐

Angular 在狀態管理上經歷了重大革新。傳統上依賴 Zone.js + 髒檢查(Dirty Checking),而現代 Angular (v16+) 則引進了 Signals 邁向細粒度更新。

1. 現代寫法:Angular Signals(推薦)

Signals 是 Angular 迎來細粒度響應式的核心 API,語法與 Vue 的 ref 有異曲同工之妙,但更為嚴謹。

import { Component, signal } from '@angular/core';

@Component({
  selector: 'app-counter',
  template: `<button (click)="handleClick()">Count: {{ count() }}</button>`
})
export class CounterComponent {
  // 1. 定義一個 WritableSignal
  count = signal(0);

  handleClick() {
    // 2. 更新狀態(使用 .update() 或 .set())
    this.count.update(val => val + 1);
  }
}

  • 記憶體視角signal() 建立了一個可被訂閱的 Getter 函式。讀取時呼叫 count(),更新時使用 .set().update()
  • 無 Zone.js 負擔:Signal 清楚知道「哪一個元件」甚至「哪一個模板片段」依賴了這個資料。當 Signal 改變時,Angular 可以跳過全樹變更偵測,直接局部刷新視圖。

2. 傳統寫法:Class Property + Zone.js

在沒有 Signals 的傳統 Angular 中,你甚至不需要呼叫任何特殊的 Hook 或 API,直接修改 Class 屬性即可:

export class CounterComponent {
  count = 0; // 普通 Class 屬性

  handleClick() {
    this.count++; // 直接修改!Zone.js 會攔截非步事件並觸發全組件樹的 Change Detection
  }
}


四、 三大框架狀態機制深度比較表

比較維度 React (useState) Vue 3 (ref / reactive) Angular (Signals / 傳統)
資料哲學 不可變(Immutable) 可變(Mutable Proxy) 可變(Signal 包裝 / 普通 Class 屬性)
語法特徵 const [val, setVal] = useState() const val = ref() / val.value++ val = signal() / val.update()
狀態變更方式 呼叫 setVal(newVal) 直接給值 val.value = newVal 呼叫 val.set()val.update()
更新機制 重新執行整個組件函式,比對 Virtual DOM 依賴收集(Proxy),精準更新受影響 DOM Signals:細粒度局部更新

傳統:Zone.js 觸發 Change Detection |
| 閉包陷阱(Stale Closure) | ⚠️ 極易發生(若未處理好依賴陣列) | ❌ 無(setup 僅執行一次,變數指標不變) | ❌ 無(Class 實體屬性天然綁定 this) |


五、 狀態機制帶來的開發體驗差異

1. 閉包陷阱(Stale Closure)問題

  • React:由於每次 Render 都是全新的函式執行,如果在 useEffect 或非步 callback(如 setTimeout)中使用了舊 Render 的狀態,就會讀取到舊的快照(Stale State)。
  • Vue 3 / Angular:因為 Vue 的 ref 物件指標固定,Angular 的 Class this 實體固定,非同步寫法中永遠能讀取到最新的 ref.valuethis.prop,徹底避開了閉包陷阱。

2. 效能優化的切入點

  • React:預設情況下,父組件 Re-render,所有子組件都會跟著重新執行。開發者需要手動使用 React.memouseMemouseCallback 來切斷不必要的 Render Tree。
  • Vue 3 & Angular Signals:因為採用「自動依賴收集」,沒用到該狀態的組件壓根就不會被通知更新,天然具備優異的渲染效能。

結語

  • **React useState** 的本質是「單向資料流與不可變性」,以重繪函式換取預測性強、易於追蹤的快照狀態。
  • **Vue 3 ref** 的本質是「透明的響應式代理」,透過 Proxy 自動追蹤依賴,給予開發者直覺的原地修改體驗。
  • Angular 則正從傳統的「Zone.js 全域變更偵測」全面演進至「Signals 細粒度響應式」,兼具了強型別與高渲染效能。

理解這三種狀態管理的底層機制,不僅能幫你在框架切換時少走彎路,更能讓你在面對效能瓶頸時,一眼看穿問題出在重繪(Re-render)還是變更偵測(Change Detection)上!


上一篇
Day 11 | 從記憶體與渲染哲學看 useRef:React、Vue 3 與 Angular 的 DOM 操作與狀態持久化比較
下一篇
Day 13 | 副作用與生命週期的終極對決:useEffect(React vs Vue 3 vs Angular)
系列文
現代函式庫與JavaScript的關係22
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言