• RxJS 系列 – 概念篇


    前言

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

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

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

    主要参考 

    鐵人賽 – 30 天精通 RxJS 系列

    鐵人賽 – 打通 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
    });

    Deferred Execution (延期执行)

    RxJS 还有一个特色是 deferred execution (延期执行).

    读历史的就知道, RxJS 是 C# LINQ 的衍生品, deferred execution 正是 LINQ 的特色之一.

    const documentClicked$ = fromEvent(document, 'click');
    setTimeout(() => {
      documentClicked$.subscribe(() => console.log('clicked'));
    }, 1000);

    fromEvent 是 document.addEventListener 的 RxJS 写法.

    当 fromEvent 调用后, RxJS 并不会马上去 addEventListener.

    而是等到 1 秒后 documentClicked$ stream 被 subscribe 后, 才去 addEventListner.

    这就是所谓的 Deferred Execution. 

    如果没有了 subscribe. 所有 RxJS 都只是 declaration 而已.

    Stream 与 Array 的关系

    上面提到 Stream 就是一个变量, 在一段时间内的改变. 如果把每一次的改变都记入起来, 那么它将会长得像 Array.

    let value = 1;
    value = 2;
    value = 3;
    const value$ = [1, 2, 3];

    而在使用 Stream 的时候, 我们通常不仅仅关注它最新的值, 我们会关注整个时间线的每一个变量. 甚至拿它们来做各做逻辑计算.

    于是在使用 RxJS 的 operators 时, 会有一种调用 Array 方法的感觉. 而许多 RxJS operators 看上去也很像 Array 的方法. 比如 map, filter, skip, take 等等

    RxJS 与 Angular 的关系

    Angular 为什么引入了 RxJS 概念? 

    其最大的原因就是为了实现 change detection. 当 Model 改变的时候 View 需要被更新, 这就是一个典型的观察者模式.

    Angular 虽然使用 RxJS, 但并没有很重, 常见的地方只有 HttpClient, Router.

    我们在写 Angular Application 的时候也不需要强制自己去写 RxJS. 适量的运用就可以了.

    Stream 的创建和 Pipe

    一个 stream 通常会以一些异步事件作为起点. 比如 setTimeout, setInterval, addEventListener.

    通常也会配上 variable

    Pipe 则是对 variable 的后续处理.

    [1, 2, 3].filter(v => true).map(v => v + 1);

    filter 和 map 就是 pipe 中的 operators, 它们负责处理 variable.

    RxJS 封装了许多 Operators 用于创建 Stream 和处理 variable.

  • 相关阅读:
    解决Linux 环境 GLIBCXX_3.4.15' not found问题
    同步和异步接口,你还有疑惑么?
    SQL中内连接和外连接的区别
    Linux常用操作指令(面试专用)
    关于支付类的一些测试关注点及异常点
    jenkins持续集成 python selenium(windows本地)
    从ghost映像.gho文件快速创建vmware虚拟机
    阿里p3c(代码规范,eclipse插件、模版,idea插件)
    logback错误日志发送邮件
    C#中的异步陷阱
  • 原文地址:https://www.cnblogs.com/keatkeat/p/16523249.html
Copyright © 2020-2023  润新知