場景概述
專案中經常有輸入框輸入的時候,向後臺發起請求獲取列表或資料。這個簡單的業務場景在開發的時候需要考慮以下幾點:
- 對使用者輸入的內容進行一些校驗
- 控制請求傳送的頻率【防抖】
- 當輸入框輸入長度為空時,恢復頁面資料至預設狀態
- 響應使用者的鍵盤動作【如 enter 進行查詢,esc 進行清空】
- 確保返回的資料是根據最後輸入的引數進行查詢的
程式碼實現
瞭解了業務需求後,我們結合 rxjs 操作符來控制 input 框實現上述功能
模板檢視 test.component.html 程式碼如下:
<div class="l-widget-notice-alarmCode">
<input
nz-input
placeholder="輸入告警編碼"
#noticeInput
/>
</div>
複製程式碼
接下來,我們使用 @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 就足夠了。
感謝您的閱讀~