了解了业务需求后,我们结合 rxjs 操作符来控制 input 框实现上述功能

模板视图 test.component.html 代码如下:

<div class="l-widget-notice-alarmCode">
   <input 
        nz-input 
        placeholder="输入告警编码" 
        #noticeInput
复制代码

接下来,我们使用 @ViewChild 属性装饰器,从模板视图中获取匹配的元素后, 通过 fromEvent 将一个该元素上的事件转化为一个Observable:

export class NoticeOverviewComponent implements OnInit, OnDestroy, AfterViewInit  {
    @ViewChild('noticeInput', {static: true}) noticeInput: ElementRef;
    // Angular 视图查询在 ngAfterViewInit 钩子函数调用前完成
    ngAfterViewInit() {
        this.bindNoticeInputEvent();
    private bindNoticeInputEvent(): void {
        const noticeInputEvent$ = fromEvent(this.noticeInput.nativeElement, 'keyup');
复制代码

接下来,我们通过 Pipe 管道操作符来操作事件流:

export class NoticeOverviewComponent implements OnInit, OnDestroy, AfterViewInit  {
    @ViewChild('noticeInput', {static: true}) noticeInput: ElementRef;
    // Angular 视图查询在 ngAfterViewInit 钩子函数调用前完成
    ngAfterViewInit() {
        this.bindNoticeInputEvent();
    private bindNoticeInputEvent(): void {
        const noticeInputEvent$ = fromEvent(
            this.noticeInput.nativeElement, 
            'keyup'
        noticeInputEvent$.pipe(
            debounceTime(300),
            filter((event: KeyboardEvent) => 
                !(event.keyCode >= 37 && event.keyCode <= 40)
            pluck('target', 'value'),
        ).subscribe(this.loadData);
    public loadData(value: string): void {
        // todo => fetch data
复制代码

上面的代码中,我们在 pipe 管道中,使用 debounceTime 操作符,舍弃掉在两次输出之间小于指定时间的发出值来完成防抖处理, 通过 filter 操作符过滤符合业务需求的发出值。

最后,我们通过 pluck 操作符来取得发出对象嵌套属性,即 event.value 属性来获取用户的输入值。

由于 Observable 是惰性的,我们需要主动去触发这个函数来获取这个值。 关于 Observable 的介绍可以 参考 Angular - Observable 概述

为了避免订阅操作可能会导致的内存泄漏,我们的请求方法还需要做取消订阅的处理。

由于 Observable 也是一种基于发布、订阅模式的推送体系,在某个时间点,我们需要执行取消订阅操作来释放系统的内存。否则,应用程序可能会出现内存泄露的情况。

Observable 订阅之后会返回一个 subscription 对象,通过调用 subscription 的 unsubscribe 方法来取消当前 Observer 的订阅,关于取消订阅,可以使用标准的模式来取消订阅:

export class NoticeOverviewComponent implements OnInit, OnDestroy, AfterViewInit  {
    @ViewChild('noticeInput', {static: true}) noticeInput: ElementRef;
    noticeInputSubscription: Subscription;
    // Angular 视图查询在 ngAfterViewInit 钩子函数调用前完成
    ngAfterViewInit() {
        this.bindNoticeInputEvent();
    // 通常我们在组件销毁时,去取消订阅。
    OnDestroy() {
        this.noticeInputSubscription.unsubscribe();
    private bindNoticeInputEvent(): void {
        const noticeInputEvent$ = fromEvent(
            this.noticeInput.nativeElement, 
            'keyup'
        this.noticeInputSubscription = noticeInputEvent$.pipe(
            debounceTime(300),
            filter((event: KeyboardEvent) => 
                !(event.keyCode >= 37 && event.keyCode <= 40)
            pluck('target', 'value'),
        ).subscribe(this.loadData);
    public loadData(value: string): void {
        // todo => fetch data
复制代码

但是这种做法过于麻烦,且一个 Subscription 对应一个 subscribe。

我们可以通过 使用 takeUntil 操作符来实现 observable 的自动取消订阅:

export class NoticeOverviewComponent implements OnInit, OnDestroy, AfterViewInit  {
    @ViewChild('noticeInput', {static: true}) noticeInput: ElementRef;
    // 创建一个在整个组件中使用的订阅对象 Subject 
    private unsubscribe: Subject<void> = new Subject<void>();
    // Angular 视图查询在 ngAfterViewInit 钩子函数调用前完成
    ngAfterViewInit() {
        this.bindNoticeInputEvent();
    // 通常我们在组件销毁时,去取消订阅。
    OnDestroy() {
        this.unsubscribe.next();
        this.unsubscribe.complete();
    private bindNoticeInputEvent(): void {
        const noticeInputEvent$ = fromEvent(
            this.noticeInput.nativeElement, 
            'keyup'
        noticeInputEvent$.pipe(
            takeUntil(this.unsubscribe),  
            debounceTime(300),
            filter((event: KeyboardEvent) => 
                !(event.keyCode >= 37 && event.keyCode <= 40)
            pluck('target', 'value'),
        ).subscribe(this.loadData);
    public loadData(value: string): void {
        // todo => fetch data
复制代码

takeUntil 接受一个 observable ,当接受的 observable 发出值的时候,源 observable 便自动完成了,利用这种机制不仅可以对单个订阅进行取消,整个组件中都可以利用同一个 unsubscribe: Subject<void> 对象来取消订阅,因为我们使用了 Subject,这是一种 多播 的模式

这种机制也是 Angular 中组件销毁时采用的取消订阅模式的基础

大多数时候,我们可以在 Pipe 最上层来设置 takeUntil 来处理订阅,但是在部分 高阶流 中,订阅者所订阅的 observable 可能是由其他流返回,这个过程也是惰性的,因此如果此时在最上方设置 takeUntil 也极有可能导致内容泄漏的问题。

takeUntil 在一些其他场景中,也有可能会引发一些问题,可以通过 配置 rxjs-tslint-rules 中的 rxjs-no-unsafe-takeuntil 规则来确保 takeUntil 的位置放置正确。在这个业务中,我们在最上方设置 takeUntil 就足够了。

感谢您的阅读~

分类:
前端
标签: