iT邦幫忙

2026 iThome 鐵人賽

DAY 11
0
Modern Web

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

Day 11 | 從記憶體與渲染哲學看 useRef:React、Vue 3 與 Angular 的 DOM 操作與狀態持久化比較

  • 分享至 

  • xImage
  •  

在前端框架的開發中,「操作真實 DOM 節點」以及「在組件生命週期中保存資料」是兩個常見的需求。在 React 中,我們第一個想到的工具通常是 useRef

但你是否曾思考過:為什麼 React 需要 useRef?為什麼在 Vue 或 Angular 中,好像不需要一個「一兼二職」的 API?

本文將從 底層渲染模型(Render Model)記憶體配置(Memory Layout) 的角度切入,一次梳理 React useRef 的本質,以及它在 Vue 3 與 Angular 中對應的實踐方式。


一、 React 的核心困境:「全函式執行」與記憶體生存戰

要理解 useRef,必須先理解 React 函式元件(Function Component)的底層運作機制。

1. 什麼是「全函式執行」?

在 React 中,「組件重新渲染(Re-render)= 整個 JavaScript 函式從頭到尾重新執行一次」

function Counter() {
  // ⚠️ 每次 re-render,此函式內部的每一行程式碼都會重新執行!
  let count = 0; 
  const [state, setState] = useState(0);

  const handleClick = () => {
    count += 1;
    setState(state + 1); // 觸發 Re-render
  };

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

setState 觸發重新渲染時:

  1. React 再次呼叫 Counter() 函式。
  2. Call Stack 建立新的執行上下文,let count = 0 被重新宣告
  3. 上一次渲染修改的 count = 1 隨即被垃圾回收(Garbage Collection),值瞬間歸零。

2. 記憶體視角:useRef 如何挽救指標?

如果把變數寫在函式外部(Global Scope),雖然記憶體位址固定,但會導致多個組件實體共享同一份資料,破壞組件獨立性。

useRef 就是 React 給出的「記憶體逃逸口」:

const timerRef = useRef(null);

  • 指標不變性(Pointer Persistence):首次掛載(Mount)時,React 在 Heap 記憶體劃分一塊空間,存入物件 { current: initialValue }
  • 跨渲染持久化:無論 Counter() 函式被重新執行多少次,useRef 永遠返回指向同一個記憶體位址的物件。
  • 修改不觸發渲染:直接變更 ref.current = newValue 是原地的物件屬性修改(Mutable Modification),不會觸發 React 的 Engine 重繪 UI。

二、 三大框架實踐對比

React 的 useRef 承擔了兩項職責:

  1. 操作真實 DOM 節點 / 組件實體
  2. 保存跨渲染且不引發 UI 變更的資料

在 Vue 3 與 Angular 中,這兩項職責從設計哲學上就被明確拆開了。

1. React:useRef 雙重職責

import { useRef } from 'react';

function MyComponent() {
  // 職責 1:綁定 DOM 節點
  const inputRef = useRef(null);
  
  // 職責 2:保存非渲染狀態資料
  const clickCount = useRef(0);

  const handleFocus = () => {
    inputRef.current?.focus();
    clickCount.current += 1; // 不會觸發 Re-render
  };

  return <input ref={inputRef} type="text" />;
}


2. Vue 3:單次 Setup 與閉包記憶體

Vue 3 Composition API 的核心哲學與 React 截然不同:<script setup> 區塊在組件生命週期中只會執行一次!後續畫面更新由細粒度的響應式系統(Proxy)精準切換。

  • 保存非渲染變數:因為 setup 只執行一次,使用一般的 JavaScript let 變數即可。藉由閉包(Closure),該變數的記憶體位址在組件生命週期內永遠存在,且變更它不會觸發 UI 更新。
  • DOM 操作:Vue 3.5+ 推出 useTemplateRef 專職處理 DOM 引用。
<script setup>
import { useTemplateRef } from 'vue'

// 職責 1:綁定 DOM (Vue 3.5+ 專用 API)
const inputRef = useTemplateRef('my-input')

// 職責 2:保存非渲染狀態資料
// 依靠 setup 的單次執行與閉包,不需要任何特殊的 Ref Hook
let clickCount = 0

const handleFocus = () => {
  inputRef.value?.focus()
  clickCount += 1
}
</script>

<template>
  <input ref="my-input" type="text" />
</template>


3. Angular:Class 物件導向與記憶體佈局

Angular 採用標準的 OOP 類別結構。當組件被實體化(new MyComponent())時,屬性天然附著在 this 實體上,保存在 Heap 記憶體中。

  • 保存非渲染變數:宣告一般的 Class 屬性即可(只要不綁定到 Template,就不會參與變更偵測)。
  • DOM 操作:在現代 Angular (v17.2+) 中使用 viewChild Signal API,或傳統的 @ViewChild 裝飾器。
import { Component, ElementRef, viewChild } from '@angular/core';

@Component({
  selector: 'app-my-component',
  template: `<input #myInput type="text" />`
})
export class MyComponent {
  // 職責 1:綁定 DOM (Angular 17+ Signal 寫法)
  inputRef = viewChild<ElementRef<HTMLInputElement>>('myInput');

  // 職責 2:保存非渲染狀態資料(Class 私有屬性)
  private clickCount = 0;

  handleFocus() {
    this.inputRef()?.nativeElement.focus();
    this.clickCount += 1;
  }
}


三、 三大框架核心比較表

框架 渲染模型(Render Model) 記憶體保存機制 DOM 引用實踐
React 每次 Re-render 重新執行整個函式 useRef(固定 Heap 記憶體指標的 { current } 物件) const el = useRef(null)
Vue 3 Setup 僅執行 1 次,Proxy 觸發更新 依靠 setup() 閉包,普通 let 變數即可持久化 const el = useTemplateRef('id')
Angular Class 實體化,依賴 Change Detection 直接作為 Class Property 附著在 this 實體上 el = viewChild('id')

四、 現代 React 趨勢:useRef 的職責分流

隨著 React 的演進(React 19+ 與 React Compiler),useRef 過去承擔的部分負擔已被新機制分流:

  1. forwardRef 走入歷史
    在 React 19 中,ref 已被升格為普通 Prop,不再需要繁瑣的 forwardRef 包裹子組件。
// React 19+:直接傳遞與接收 ref prop
function CustomInput({ ref, ...props }) {
  return <input ref={ref} {...props} />;
}

  1. Ref Callback 支持 Cleanup
    綁定 DOM 元素時,可直接在 Ref Callback 中返回清理函式,不再需要硬寫 useEffect 搭配 useRef
<input ref={(node) => {
  // 元素掛載
  return () => { /* 元素卸載清理 */ };
}} />

  1. 專職 Hook 接管 UI 狀態
    樂觀更新轉由 useOptimistic 處理,表單與非同步狀態交給 useActionStateuseRef 不再被濫用為狀態管理的「臨時避難所」。

結語

  • **React 的 useRef** 是在「全函式執行」模型下,維護固定記憶體指標與存取 DOM 的必要逃逸機制。
  • Vue 3 透過「單次 Setup + 閉包」解耦了變數保存,並用 useTemplateRef 專職 DOM 操作。
  • Angular 則憑藉「Class 實體」的 OOP 天性,讓變數天然存活於生命週期中。

理解各框架在「渲染」與「記憶體」上的底層設計哲學,能讓我們在選擇工具時更加明晰,寫出更高效、優雅的代码。


上一篇
Day 10 | 從 JS 底層物件機制看三大框架:從 static/instance 方法到 Property Descriptor 如何塑造 Vue、React 與 Angular
下一篇
Day 12 | 響應式與不可變的碰撞:從狀態機制看 useState(React vs Vue 3 vs Angular)
系列文
現代函式庫與JavaScript的關係22
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言