Skip to content

响应式系统

基本概念

响应式系统

  • 响应式系统(Reactive System)是能够根据状态自动更新视图的系统。
  • 响应式系统的作用是将状态与视图关联起来,当状态发生变化时,自动更新视图。
  • 响应式系统的实现通常使用观察者模式,即当状态发生变化时,通知所有观察者进行更新。

观察者模式

  • 观察者模式是一种设计模式,用于定义对象之间的一对多依赖关系,并实现当一个对象状态改变时,所有依赖于它的对象都会得到通知并自动更新。
  • 观察者模式包含两个角色:观察者和被观察者。观察者订阅被观察者,当被观察者状态发生变化时,它会通知所有观察者进行更新。

状态

  • 状态是指对象所处的某种特定的状态,它可以是任何类型的数据,如布尔值、整数、字符串等。
  • 在响应式系统中,状态通常指的是数据模型中的数据,它可以是任何类型的数据,如模型对象、数据库中的数据等。

视图

  • 视图是指用户界面的一部分,它可以是任何类型的组件,如按钮、输入框、列表等。
  • 在响应式系统中,视图通常指的是与状态关联的组件,当状态发生变化时,它会自动更新以显示最新的数据。

依赖注入

  • 依赖注入(Dependency Injection)是一种设计模式,用于解决对象之间的依赖关系。
  • 在响应式系统中,依赖注入通常用于将状态注入到视图中,以便在状态发生变化时,视图能够自动更新。

实现

Vue 2 使用 getter / setters 完全是出于支持旧版本浏览器的限制。而在 Vue 3 中则使用了 Proxy 来创建响应式对象。

js
// `Dep` 类是一个依赖管理器,它内部维护了一个订阅者集合(subscribes)。
// `addSubscribe` 方法用于添加订阅者,`notify` 方法用于通知所有订阅者更新
class Dep {
  constructor() {
    this.subscribes = new Set();
  }
  addSubscribe(watcher) {
    this.subscribes.add(watcher);
  }
  notify() {
    this.subscribes.forEach(watcher => watcher());
  }
}
// `activeUpdate` 是一个全局变量,用于记录当前正在执行的更新函数。
// `autorun` 函数接受一个更新函数,当数据发生变化时,这个更新函数会被调用。
let activeUpdate = null;
function autorun(update) {
  function wrappedUpdate() {
    activeUpdate = wrappedUpdate;
    update();
    activeUpdate = null;
  }
  wrappedUpdate();
}
// `observable` 函数接受一个对象,然后遍历这个对象的所有属性,
// 使用`Object.defineProperty`方法劫持每个属性的getter和setter。
// 在getter中收集依赖,在setter中触发更新。
function observable(data) {
  Object.keys(data).forEach(key => {
    const dep = new Dep();
    let value = data[key];
    Object.defineProperty(data, key, {
      get() {
        dep.addSubscribe(activeUpdate);
        return value;
      },
      set(newValue) {
        if (newValue === value) return;
        value = newValue;
        dep.notify();
      }
    })
  })
  return data
}

// 使用示例
const state = observable({
  name: 'Alice'
});

autorun(() => {
  console.log(state)
  console.log(`Name is: ${state.name}`);
});

setTimeout(() => {
  state.name = 'Bob';
}, 5000);

解释

以上实例中,我们创建了一个可观察的对象state,并且通过autorun函数模拟了一个绑定到UI的操作。当我们修改state.name时,这个变化会通过setter触发,然后Dep实例会通知所有依赖更新,即重新执行autorun中传入的函数,从而实现了数据到UI的同步。 要实现UI到数据的同步,通常需要监听UI上的事件(例如输入框的input事件),然后在事件处理函数中更新数据模型。这部分通常由框架自动完成。

思考

如何使用proxy实现双向绑定?

Released Under The MIT License.