從前端開發角度了解回應式 (Reactivity)

2025年12月20日

💎 加入 E+ 成長計畫 與超過 800+ 位工程師一同在社群成長,並獲得更多深度的軟體前後端學習資源

回應式程式設計 (reactive programming) 是在軟體開發中非常重要的概念之一,在近年來的各大前端框架,都有關於回應式的設計。舉例來說 Vue 的 ref ,或是 React 的 useStateuseEffect,以及其他框架有的 Signals 都是回應式 (reactivity) 的實踐。因此希望透過這篇主題文,來探討這個概念,讓讀者們可以更了解不同的回應式設計。

在往下談之前,想先釐清一下,在中文翻譯中,許多文件會把 reactivity 翻譯成響應式。但因為在前端開發中,有另一個概念叫 responsive design 也會被翻譯成響應式設計,為了避免混淆,在這篇主題文會用「回應式」這個中文翻譯。

什麼是回應式 (reactivity)?

在具體談回應式是什麼之前,先讓我們來看下面這段程式碼。假設今天在某一個 JavaScript 的面試中,面試官問說下面的 console.log 會印出什麼結果,大家的回答會是什麼呢?

let celsius = 20;
let fahrenheit = (celsius * 9) / 5 + 32;

console.log(`華氏溫度是: ${fahrenheit}`);

celsius = 30;

console.log(`華氏溫度是: ${fahrenheit}`);

上面這段程式碼的 console.log 會印出

華氏溫度是: 68;
華氏溫度是: 68;

這時問題就來了,明明 celcius 有被更新成 30,為什麼第二次印出來的 fahrenheit 仍然是 68 呢? 為什麼不是 86 呢?

這是因為對 fahrenheit 來說,在  let fahrenheit = (celsius * 9) / 5 + 32 這行在做的事,其實是把算出來的 68 賦予給 fahrenheit,所以實際理解起來會更接近下面這樣,相信下面這個版本會更讓人能理解,為什麼 celcius 改變後,fahrenheit 沒有跟著變

let celsius = 20;
let fahrenheit = 68; // 對 JavaScript 來說,是直接賦予 (celsius * 9) / 5 + 32; 的結果

console.log(`華氏溫度是: ${fahrenheit}`);

celsius = 30;

console.log(`華氏溫度是: ${fahrenheit}`);

從上面這段可以看到,因為 fahrenheitcelsius 之間沒有一個持續的連結或所謂的「依賴關係」,當 celsius = 30; 這行執行時,celsius 值會更新,但 fahrenheit 不會感知到這個變化,自然也不會自動重新執行最初的計算公式。所以 fahrenheit 的值仍會是 68。

但這種狀況顯然不理想,更理想的狀況會是,當 celsius 改變時,我們不用特別重新賦予 fahrenheitfahrenheit 也會根據最新的 celsius 跟著更新。而當能夠做到這一點,就可以說是回應式的 (reactive) 程式。

具體來說,是回應式 (reactivity) 的定義如下 (這段取自社群中相當熱門的 JavaScript 框架 Solid.js 的文件,因為對比各種寫法,覺得這個定義最精闢扼要)

回應式這種程式典範,是指一個系統能自動對資料或狀態的變化,做出回應的能力,這能確保應用程式與其底層資料保持同步更新。

This programming paradigm refers to a system's ability to respond to changes in data or state automatically, ensuring applications stay up-to-date with their underlying data.

上面這個定義讀起來可能還是有一點抽象,讓我們用大家熟知的 Excel (或 Google Sheet) 為例來說明。假如有用過 Excel,大概對下面這個截圖不會陌生,當今天把 A3 設定成 A1 + A2,這時只要 A1 或者 A2 有任何改變,A3 都會自動跟著更新。這就是回應式程式的體現,能在不需用額外操作下,就讓 A3 的資料跟著同步更新。

Excel 體現了回應式的概念
Excel 體現了回應式的概念

為什麼前端開發需要回應式?

在了解回應式是什麼後,接著讓我們回到前端開發的角度來討論,為什麼回應式重要?

以上面談到的 Excel 的例子,這種「當改變了原始值,基於該原始值的所有欄位也都會改變」在前端開發中基本上是必備的。在前端開發的領域中,有很多情況是某個值會依賴某個資料來源,因此當資料來源變動時,值跟著同步變化才能確保畫面上顯示的內容是正確的。

具體來說的同步,是指資料來源跟 DOM 的同步。假設在沒有特別做回應式的狀況下,過去要更新 DOM,就需要寫 document.querySelector 這種選擇器,然後找到要更新的 DOM 元素,然後去改變該 DOM 元素的內容。

但是在現代框架幫忙處理好回應式的狀況,開發者可以不用去管那些繁瑣且重複的操作,可以直接寫要什麼值,然後畫面就會直接呈現相對應的值,這也是在 用宣告式 (declarative) 的方式寫程式 一文有談到的宣告式典範。

以 React 來說,在下面的例子中,在按鈕 onClick 中會呼叫 setCelsius(30),因此在點擊按鈕時,celcius 被更新,這時因為 React 幫忙處理好回應式的部分,依賴 celsius 而生的fahrenheit 就會自動跟著更新。

import { useState, useMemo, useCallback } from "react";

function TemperatureConverter() {
  const [celsius, setCelsius] = useState(20);
  const fahrenheit = useMemo(() => (celsius * 9) / 5 + 32, [celsius]);
  const updateTemperature = useCallback(() => setCelsius(30), []);

  return (
    <div>
      <p>華氏溫度是: {fahrenheit}</p>
      <button onClick={updateTemperature}>將攝氏度設為 30</button>
    </div>
  );
}

同樣地,如果在 Vue 當中,@click 中綁定更新攝氏溫度的函式,點下去後會把 celsiusvalue 更新成 30,這時因為 Vue 的回應式機制,即使不用額外手動去更新 fahrenheitfahrenheit 的值也會自動跟著更新。

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

const celsius = ref(20)

const fahrenheit = computed(() => {
  return (celsius.value * 9) / 5 + 32
})

function updateTemperature() {
  celsius.value = 30
}
</script>

<template>
   <div>
     <p>華氏溫度是: {{ fahrenheit }} </p>
     <button @click="updateTemperature"> 將攝氏度設為 30</button>
   </div>
</template>

上面這兩個例子,都展示了當有回應式的機制後,當我們改變了某個基底的值,所有依賴該值的其他值,也會自動更新,這能在免去繁瑣的 DOM 操作下,也能確保資料與 UI 畫面的一致性。

閱讀更多

如果讀者們想更了解回應式這個主題,包含如何實作回應式、粗粒度 (coarse-grained) 與細粒度 (fine-grained) 回應式的區別是什麼等,我們在 E+ 成長計畫的主題文中,都有更詳細談。感興趣的讀者,可以在以下連結看到 E+ 的詳細介紹 (連結)。

本文為 E+ 成長計畫的深度內容,截取段落開放免費閱讀。歡迎加入 E+ 成長計畫閱讀完整版本 (點此了解 E+ 的詳細介紹)。

🧵 如果你想收到最即時的內容更新,可以在 FacebookInstagram 上追蹤我們