RxJS 系列 – 概念篇

前言

很长一段时间没有写 Angular 了 (哎...全栈的命), 近期计划又要开始回去写了. 于是就开始做复习咯.

我的复习是从 JS > TS > RxJS > Angular, 与此同时当然是顺便写一系列半教程半复习的文章咯, 我当年写了许多 Angular 的学习笔记, 只是文笔太烂, 这次得好好写了.

JS 已经复习到七七八八了, TS 老是提不起劲去写, 所以就改成边写 TS 边写 RxJS 吧.

 

以前写过相关的文章:

angular2 学习笔记 ( Rxjs, Promise, Async/Await 的区别 )

angular2 学习笔记 ( rxjs 流 )

 

什么是流 (stream) ?

RxJS 参杂了许多概念, 什么函数式, 观察者, 异步等等...

但我个人觉得最容易理解的部分是 stream (流).

stream 是什么? 它表示一个时间内一个变化的状态.

在 JS 里, 状态可以被理解为某个值, variable 的值.

时间则是用户使用 App 的时间.

看例子吧:

上图 (gif) 大概是 5 秒钟, 这个就是时间. 在这 5 秒中里面, 价钱 (值) 变化了好几次 (160, 190, 200, 250) 

一个有时间, 有变化的值就可以了解为一个 stream.

 

Why Stream? Because... 管理

为什么要用 "stream" 概念去理解这些 "值" ? 不能简单的理解为 "点击" > "更新 value" ?

当然可以, 其实 stream 概念并不是为了理解, 而是为了管理.

当程序里出现越来越多, 变来变去的值以后, 出现 bug 的机率就越来越高, 而追踪 bug 也越来越吃力. 

所以就必须整一套概念来管理它们. 这就好比你用 Redux 来管理 React 的 state 一样.

以前有许多人拿 redux 去管理简单的程序, 结果就是大材小用, 反而是 redux 本身增加了整个系统的复杂度...幸好后来出现了 hook 才把这群人拉了出来...(永远记得, 软件开发一定要看清楚当前项目需求, 选择合适的方案而不是最牛逼的方案)

 

观察者模式

上面提到了, stream 的其中一个特色就是变化. 一个东西变化了, 那么依赖它的东西通常也会跟着变化. 蝴蝶效应...

我们在写 Excel 的时候经常会写这样的逻辑 cell

 

full name 这个值, 来自 first name + ' ' + last name

而每当 first name 或 last name 变化以后, full name 也随之变化. 

在上面这个例子里, first name, last name 就是 stream. 随着时间它会发生变化.

而 full name 算是一个 depend and addon stream. 它也会变化, 同时它依赖其它的 stream 和一些而外的处理逻辑. 

用 RxJS 来表达这类型的场景会非常贴切.

体验一下:

Without RxJS 实现:

const firstName = document.querySelector<HTMLInputElement>('#first-name')!;
const lastName = document.querySelector<HTMLInputElement>('#last-name')!;
const fullName = document.querySelector<HTMLSpanElement>('#full-name')!;

for (const input of [firstName, lastName]) {
  input.addEventListener('input', () => {
    fullName.textContent = `${firstName.value} ${lastName.value}`;
  });
}

用 RxJS 来实现:

const firstNameInput = document.querySelector<HTMLInputElement>('#first-name')!;
const lastNameInput = document.querySelector<HTMLInputElement>('#last-name')!;
const fullNameSpan = document.querySelector<HTMLSpanElement>('#full-name')!;

// 表达 stream
const firstName$ = fromEvent(firstNameInput, 'input').pipe(
  map(() => firstNameInput.value),
  startWith(firstNameInput.value)
);
const lastName$ = fromEvent(lastNameInput, 'input').pipe(
  map(() => lastNameInput.value),
  startWith(lastNameInput.value)
);
const fullName$ = combineLatest([firstName$, lastName$]).pipe(
  map(([firstName, lastName]) => `${firstName} ${lastName}`)
);

// 消费 stream
fullName$.subscribe(fullName => {
  fullNameSpan.textContent = fullName;
});

哇...怎么更复杂了...所以啊, 上面说了, 程序简单就没必要搞 RxJS 啊.

但你看看它的管理是不错的, 表达 stream 负责描述 stream 的来源.

尤其是那个 combine stream 的表达尤其加分.

消费 stream 则可以做许多事情 (比如 render view).

这样 stream 可以被多个地方复用.

赠送一个优化版本:

// 这个可以封装起来
function fromInput(input: HTMLInputElement): Observable<string> {
  return fromEvent(input, 'input').pipe(
    map(() => input.value),
    startWith(input.value)
  );
}

// 表达 stream
const firstName$ = fromInput(document.querySelector<HTMLInputElement>('#first-name')!);
const lastName$ = fromInput(document.querySelector<HTMLInputElement>('#last-name')!);
const fullName$ = combineLatest([firstName$, lastName$]).pipe(
  map(([firstName, lastName]) => `${firstName} ${lastName}`)
);

// 消费 stream
const fullNameSpan = document.querySelector<HTMLSpanElement>('#full-name')!;
fullName$.subscribe(fullName => {
  fullNameSpan.textContent = fullName; // render view
});

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章