观察者模式

使用观察者模式,我们可以将某些对象(观察者)订阅到另一个称为 observable(可观察) 的对象。
每当一个事件发生时,observable 就会通知它所有的观察者!

一个 observable 对象通常包含 4 个重要部分:

  1. observers:观察者数组,每当发生特定事件时都会收到通知
  2. subscribe():将观察者添加到观察者数组的方法
  3. unsubscribe():从观察者数组中移除观察者的方法
  4. notify():一种在特定事件发生时通知所有观察者的方法

创建一个 observable

让我们创建一个 observable! 一种简单方法是使用 ES6 类。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21

class Observable {
  constructor() {
    this.observers = [];
  }

  subscribe(func) {
    this.observers.push(func); 
    //push() 方法将一个或多个元素添加到数组的末尾,并返回该数组的新长度。
  }

  unsubscribe(func) {
    this.observers = this.observers.filter(observer => observer !== func);
    //filter() 方法创建一个新数组, 它返回所提供函数处理过的所有元素。  
  }

  notify(data) {
    this.observers.forEach(observer => observer(data));
    //forEach() 方法对数组的每个元素执行一次给定的函数。
  }
}

可以使用 subscribe 方法将观察者添加到观察者数组中,使用 unsubscribe 方法删除观察者,并使用 notify 方法通知所有订阅者。

让我们用这个 observable(可观察) 构建一些东西。有一个非常基本的应用程序,它只包含两个组件:按钮和开关。

export default function App() {
  return (
    <div className="App">
      <Button>Click me!</Button>
      <FormControlLabel control={<Switch />} />
    </div>
  );
}

我们希望跟踪用户与应用程序的交互。 每当用户单击按钮或切换开关时,我们都希望使用时间戳记录此事件。
除了记录它,我们还想创建一个 Toast 通知,在事件发生时显示它!

本质上,我们想要做的是以下内容:

每当用户调用 handleClick 或 handleToggle 函数时,这些函数都会调用观察者上的通知方法。
notify 方法将通过 handleClick 或 handleToggle 函数传递的数据通知所有订阅者!

首先,让我们创建 logger 和 toastify 函数。 这些函数最终会从 notify 方法接收数据。

import { ToastContainer, toast } from "react-toastify";

function logger(data) {
  console.log(`${Date.now()} ${data}`);
}

function toastify(data) {
  toast(data);
}

export default function App() {
  return (
    <div className="App">
      <Button>Click me!</Button>
      <FormControlLabel control={<Switch />} />
      <ToastContainer />
    </div>
  );
}

目前,logger 和 toasity 函数不知道 observable:observable 还不能通知它们!
为了让它们成为观察者,我们必须使用 observable 上的 subscribe 方法订阅它们!

import { ToastContainer, toast } from "react-toastify";

function logger(data) {
  console.log(`${Date.now()} ${data}`);
}

function toastify(data) {
  toast(data);
}

observable.subscribe(logger);
observable.subscribe(toastify);

export default function App() {
  return (
    <div className="App">
      <Button>Click me!</Button>
      <FormControlLabel control={<Switch />} />
      <ToastContainer />
    </div>
  );
}

每当事件发生时,logger 和 toastify 函数都会收到通知。
现在我们只需要实现实际通知 observable 的函数:handleClick 和 handleToggle 函数!
这些函数应该调用 observable 上的 notify 方法,并传递观察者应该接收的数据。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

import { ToastContainer, toast } from "react-toastify";

function logger(data) {
  console.log(`${Date.now()} ${data}`);
}

function toastify(data) {
  toast(data);
}

observable.subscribe(logger);
observable.subscribe(toastify);

export default function App() {
  function handleClick() {
    observable.notify("用户点击按钮!");
  }

  function handleToggle() {
    observable.notify("用户拨动开关!");
  }

  return (
    <div className="App">
      <Button>Click me!</Button>
      <FormControlLabel control={<Switch />} />
      <ToastContainer />
    </div>
  );
}

Awesome! 我们刚刚完成了整个流程:

1
2
3
4
5
6

1. ---> `handleClick 和 handleToggle 调用观察者上的通知方法`

    2. ---> `观察者通知订阅者:logger 和 toastify 函数`

        3. ---> `每当用户与任一组件交互时,logger 和 toastify 函数都会收到 notify 方法的通知!

尽管我们可以通过多种方式使用观察者模式,但它在处理基于事件的异步数据时非常有用。

  • 也许您希望某些组件在某些数据下载完成时收到通知
  • 或者每当用户向留言板发送新消息并且所有其他成员都应该收到通知时

案例分析 RxJS

使用可观察模式的流行库是 RxJS。

ReactiveX 将观察者模式与迭代器模式以及函数式编程与集合相结合,以满足对管理事件序列的理想方式的需求。 - RxJS

使用 RxJS,我们可以创建 observable 并订阅某些事件! 让我们看一个在他们的文档中介绍的例子,它记录了用户是否在文档中拖动。

RxJS 有大量使用可观察模式的内置功能和示例。


优点

使用观察者模式是强制分离关注点和单一职责原则的好方法。
观察者对象与可观察对象并不紧密耦合,并且可以随时(解)耦合。
observable 对象负责监控事件,而观察者只是处理接收到的数据。


缺点

如果观察者变得过于复杂,则在通知所有订阅者时可能会导致性能问题。


知识点

  • Array.prototype.push()
  • Array.prototype.filter()
  • Array.prototype.forEach()