从前端开发角度了解响应式 (Reactivity)
2025年12月20日
响应式编程 (reactive programming) 是在软件开发中非常重要的概念之一,在近年来的各大前端框架,都有关于响应式的设计。举例来说 Vue 的 ref ,或是 React 的 useState 与 useEffect,以及其他框架有的 Signals 都是响应式 (reactivity) 的实践。因此希望透过这篇主题文,来探讨这个概念,让读者们可以更了解不同的响应式设计。
在往下谈之前,想先厘清一下,在中文翻译中,许多文件会把 reactivity 翻译成"响应式"。这篇主题文会用"响应式"这个中文翻译。
什么是响应式 (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}`);
从上面这段可以看到,因为 fahrenheit 和 celsius 之间没有一个持续的连结或所谓的"依赖关系",当 celsius = 30; 这行执行时,celsius 值会更新,但 fahrenheit 不会感知到这个变化,自然也不会自动重新执行最初的计算公式。所以 fahrenheit 的值仍会是 68。
但这种状况显然不理想,更理想的状况会是,当 celsius 改变时,我们不用特别重新赋予 fahrenheit,fahrenheit 也会根据最新的 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 的例子,这种"当改变了原始值,基于该原始值的所有栏位也都会改变"在前端开发中基本上是必备的。在前端开发的领域中,有很多情况是某个值会依赖某个资料来源,因此当资料来源变动时,值跟着同步变化才能确保画面上显示的内容是正确的。
具体来说的同步,是指资料来源跟 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 中绑定更新摄氏温度的函式,点下去后会把 celsius 的 value 更新成 30,这时因为 Vue 的响应式机制,即使不用额外手动去更新 fahrenheit,fahrenheit 的值也会自动跟着更新。
<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+ 的详细介绍)。