响应式系统的核心是"数据变化驱动行为变化"。这一模块先建立范式认知,再进入框架实现细节。

什么是响应式编程

响应式编程(Reactive Programming)是一种面向**数据流(Data Stream)变更传播(Change Propagation)**的声明式编程范式。它的核心思想是:

当数据源发生变化时,依赖该数据的所有目标自动更新,而无需手动干预。

命令式 vs 声明式

理解响应式的第一步是区分两种编程风格:

// ===== 命令式:描述"如何做" =====
// 每一步都手动操作 DOM
const count = 0;
const el = document.getElementById('count');
el.textContent = count;

document.getElementById('btn').addEventListener('click', () => {
  // 手动修改状态
  const newValue = parseInt(el.textContent) + 1;
  // 手动同步到视图
  el.textContent = newValue;

  // 如果还有其他地方依赖这个值,也要手动更新
  document.title = `计数: ${newValue}`;
});
// ===== 声明式:描述"做什么" =====
// 只需定义数据和视图的关系,框架自动处理同步
const state = reactive({ count: 0 });

function render() {
  return h('div', [
    h('span', { id: 'count' }, state.count),
    h('button', { onClick: () => state.count++ }, '加一'),
  ]);
}

// state.count 变化时,render 自动重新执行

关键区别:

维度

命令式

声明式(响应式)

关注点

如何一步步实现

要达到什么效果

状态同步

手动维护

自动传播

数据流追踪

需要开发者记忆

框架自动追踪

复杂度增长

线性甚至超线性增长

近似线性

调试心智

控制流清晰

追踪链路更抽象

数据流

数据流是响应式系统的血管。理解数据如何流动,才能设计出可预测的状态架构。

单向数据流

┌─────────┐     ┌──────────┐     ┌──────┐
│  Action  │ ──▶ │  Store   │ ──▶ │ View  │
│ (用户操作)│     │ (状态)    │ ◀──│      │
└─────────┘     └──────────┘     └──────┘
                       ▲               │
                       └──── Event ────┘
// 单向数据流的本质:
// 1. State 是唯一的真相来源
// 2. State 只能通过 Action 改变
// 3. View 是 State 的投影
// 4. 用户交互产生 Action

const store = {
  state: { count: 0 },

  dispatch(action) {
    switch (action.type) {
      case 'INCREMENT':
        this.state.count += action.payload ?? 1;
        break;
    }
    this.notify();  // 通知所有观察者
  },
};

双向绑定

双向绑定是单向数据流的便捷语法糖:

View ──(用户输入)──▶ State
  ▲                    │
  └────(状态变更)──────┘
<!-- Vue 的 v-model 就是双向绑定的典型 -->
<input v-model="username" />
<!-- 等价于 -->
<input :value="username" @input="username = $event.target.value" />

双向绑定在简单表单场景非常高效,但过度使用会导致数据流向混乱。建议遵循原则:组件内部可用双向绑定,跨组件传递使用单向数据流

观察者模式

观察者模式(Observer Pattern)是响应式系统的基石之一。

经典实现

class Subject {          // 被观察者(主题)
  #observers = new Set();

  subscribe(observer) {
    this.#observers.add(observer);
    // 返回取消订阅函数
    return () => this.#observers.delete(observer);
  }

  unsubscribe(observer) {
    this.#observers.delete(observer);
  }

  notify(data) {
    for (const observer of this.#observers) {
      observer.update(data);
    }
  }
}

class Observer {         // 观察者
  constructor(name) {
    this.name = name;
  }
  update(data) {
    console.log(`[${this.name}] 收到通知:`, data);
  }
}

// 使用
const subject = new Subject();

const sub1 = subject.subscribe(new Observer('A'));
const sub2 = subject.subscribe(new Observer('B'));

subject.notify({ value: 1 });  // A 和 B 都收到
sub1();                        // 取消 A 的订阅
subject.notify({ value: 2 });  // 只有 B 收到

观察者在 Vue 中的体现

// Vue 组件本质上就是一个 Observer
export default {
  data() {
    return { message: 'hello' };
  },
  // 当 data.message 变化时,Vue 自动触发 re-render
  // 这就是观察者模式的实际应用
};

发布订阅模式

发布订阅模式(Pub/Sub)是观察者模式的解耦版本——引入中间的**事件总线(Event Bus)**作为调度中心。

发布者 ──▶ 事件总线(Event Bus/EventEmitter)  ──▶ 订阅者
              │
              ├─▶ 订阅者 A
              ├─▶ 订阅者 B
              └─▶ 订阅者 C
class EventEmitter {
  #events = new Map();

  on(event, listener) {
    if (!this.#events.has(event)) {
      this.#events.set(event, []);
    }
    this.#events.get(event).push(listener);

    return () => this.off(event, listener);  // 返回取消函数
  }

  off(event, listener) {
    const listeners = this.#events.get(event);
    if (listeners) {
      const index = listeners.indexOf(listener);
      if (index > -1) listeners.splice(index, 1);
    }
  }

  emit(event, ...args) {
    const listeners = this.#events.get(event);
    if (listeners) {
      for (const listener of [...listeners]) {
        listener(...args);  // 浅拷贝防止遍历时修改
      }
    }
  }

  once(event, listener) {
    const wrapper = (...args) => {
      listener(...args);
      this.off(event, wrapper);
    };
    this.on(event, wrapper);
  }
}

// 使用示例
const bus = new EventEmitter();

// 订阅
const unsub = bus.on('user:login', (user) => {
  console.log(`${user.name} 已登录`);
});

// 发布
bus.emit('user:login', { name: 'Alice' });

// 取消
unsub();

观察者 vs 发布订阅

特征

观察者模式

发布订阅模式

耦合度

观察者需要知道被观察者的存在

发布者和订阅者完全解耦

通信方式

直接通信

通过事件总线中转

典型场景

Vue 的 Watcher → Dep

Node.js EventEmitter、Vue 2 EventBus

关系

一对多直接关联

多对多间接关联

响应式宣言

响应式系统应具备的核心特性,可以归纳为以下四个维度:

1. 响应性(Responsiveness)

系统必须能够及时感知并传播变化:

const a = reactive(1);
const b = computed(() => a * 2);  // b = 2

a = 3;  // b 应该立即变为 6,无需手动调用任何方法

2. 透明性(Transparency)

响应式行为对使用者应该是透明的——不需要关心底层如何工作:

const state = reactive({
  user: { name: 'Alice', age: 25 },
});

// 直接读写即可,无需特殊 API
state.user.name = 'Bob';  // 自动触发更新
console.log(state.user.age);  // 自动建立依赖

3. 可组合性(Composability)

响应式数据应该可以自由组合和派生:

const firstName = ref('Alice');
const lastName = ref('Smith');

const fullName = computed(() => `${firstName.value} ${lastName.value}`);
const greeting = computed(() => `Hello, ${fullName.value}!`);

// 修改源头会级联影响所有派生值
firstName.value = 'Bob';
// fullName 自动变为 "Bob Smith"
// greeting 自动变为 "Hello, Bob Smith!"

4. 可预测性(Predictability)

相同的输入始终产生相同的输出,且变更路径可追踪:

const source = ref(0);
const trace = [];

watchEffect(() => {
  trace.push(source.value);  // 完整记录每次变化
});

source.value = 1;  // trace: [0, 1]
source.value = 2;  // trace: [0, 1, 2]

响应式系统的核心循环

无论框架如何封装,每个响应式系统都在执行同一个最小闭环:

┌──────────────────────────────────────────────┐
│                                              │
│   ┌──────────┐    收集依赖     ┌──────────┐  │
│   │  读取数据  │ ──────────────▶ │ Dep/Track │  │
│   │ (Trigger) │                │ (收集阶段) │  │
│   └──────────┘                 └──────────┘  │
│         ▲                            │        │
│         │                            ▼        │
│         │                   ┌──────────┐     │
│         │    触发通知        │ Watcher/  │     │
│         └────────────────── │ Effect    │     │
│     修改数据 (Set/Patch)     │ (副作用)  │     │
│                              └──────────┘     │
│                                              │
└──────────────────────────────────────────────┘

三个关键动作:

  1. 追踪(Track):读取响应式数据时,记录谁在读

  2. 触发(Trigger):写入响应式数据时,通知所有被记录的依赖

  3. 调度(Schedule):决定何时以及如何执行副作用(同步/异步/批量)

小结

概念

核心要点

声明式 vs 命令式

描述"做什么"而非"怎么做",降低状态同步的心智负担

数据流

单向数据流保证可预测性;双向绑定是语法糖,适合局部使用

观察者模式

Subject ↔ Observer 直接耦合,一对多通知

发布订阅

引入 Event Bus 解耦发布者和订阅者,多对多通信

响应式核心循环

Track(收集依赖)→ Trigger(触发更新)→ Schedule(调度执行)

理解这些基础概念后,下一章将用原生 JavaScript 从零构建一个最小化的响应式系统。