ReplaySubject: RxJS 中的「時間記憶者」

什麼是 ReplaySubject?

ReplaySubject 是 RxJS 中的一種特殊型態 Subject,可以想像成一個「帶有記憶功能的訊息傳遞者」。它能記住之前發送過的一定數量的值,並在新的訂閱者訂閱時,自動將這些值重新發送給新訂閱者。

簡單比喻

想像 ReplaySubject 是一個教室的監視錄影系統:

  • 系統會不斷錄製教室中發生的事情(發出的值)

  • 系統可以設定儲存最近 N 小時的錄影(記憶的值數量)

  • 當有新學生(新訂閱者)進入教室時,系統會先播放錄好的內容,讓新學生了解之前發生了什麼,然後繼續實時播放新內容

與其他 Subject 的區別

Subject 類型
行為特性

Subject

只發送訂閱後的新事件

BehaviorSubject

立即給新訂閱者最後一個值,然後發送新事件

ReplaySubject

給新訂閱者發送指定數量的歷史值,然後發送新事件

AsyncSubject

只在完成時發送最後一個值

ReplaySubject 基本用法

建立 ReplaySubject

// 建立一個會記住最後 2 個值的 ReplaySubject
const replay = new ReplaySubject<number>(2);

// 也可以加入時間限制(只重播 500 毫秒內的值)
const timeReplay = new ReplaySubject<number>(2, 500);

發送與訂閱

const replay = new ReplaySubject<number>(3); // 記住最後 3 個值

// 發送一些值
replay.next(1);
replay.next(2);
replay.next(3);
replay.next(4);

// 第一位訂閱者會收到最後 3 個值:2, 3, 4
replay.subscribe(value => {
  console.log('訂閱者 1:', value);
});

// 再發送一個值
replay.next(5);

// 第二位訂閱者會收到:3, 4, 5
replay.subscribe(value => {
  console.log('訂閱者 2:', value);
});

實際應用場景

1. 使用者操作的歷史記錄

需要記住使用者最近的幾個操作,以便提供「復原」功能:

// 記住最後 10 個操作
const userActions = new ReplaySubject<UserAction>(10);

function performAction(action: UserAction) {
  // 執行操作
  action.execute();
  
  // 記錄操作以便復原
  userActions.next(action);
}

// 復原功能可以訂閱並取得最近的操作

2. 處理延遲載入的元件

確保元件無論何時被載入,都能獲取最新狀態:

// 全域服務中的狀態管理
@Injectable({ providedIn: 'root' })
export class AppStateService {
  // 記住最後 1 個應用程式狀態
  private appState = new ReplaySubject<AppState>(1);
  
  // 元件可以訂閱獲取狀態
  getState() {
    return this.appState.asObservable();
  }
  
  // 更新狀態
  updateState(newState: AppState) {
    this.appState.next(newState);
  }
}

3. 表單元件

例如在 Select 元件中處理選項過濾:

@Component({...})
export class SelectComponent {
  @Input() options: Option[] = [];
  
  // 過濾後的選項
  filteredOptions = new ReplaySubject<Option[]>(1);
  
  ngOnInit() {
    // 初始化選項
    this.filteredOptions.next(this.options);
  }
  
  // 搜尋過濾處理
  onSearch(term: string) {
    const filtered = this.options.filter(
      option => option.label.includes(term)
    );
    this.filteredOptions.next(filtered);
  }
}

適合使用 ReplaySubject 的時機

當你遇到以下情況時,考慮使用 ReplaySubject:

  1. 需要給延遲訂閱者提供之前的值:例如延遲載入的元件需要之前的系統狀態

  2. 需要記住特定數量的歷史事件:例如操作記錄、最近的輸入歷史等

  3. 處理非同步初始化:當訂閱可能發生在數據初始化之前或之後,都需要保證訂閱者能得到數據

  4. 解決競態條件:多個元件訂閱同一個數據源,需要確保所有元件都能收到相同的初始值

  5. 實現「快照」功能:需要在特定時刻捕獲並持久化系統的狀態

需要注意的事項

  • ReplaySubject 會在記憶體中保存歷史值,如果值很大或數量很多,可能會導致記憶體問題

  • 如果只需要最後一個值,考慮使用 BehaviorSubject

  • 可以指定時間限制,只重播特定時間範圍內的值

  • 完成(complete)後,ReplaySubject 會維持最後的狀態,但不再接收新值

總結

ReplaySubject 是一個強大的「時間記憶者」,它記住過去發生的事件,讓新訂閱者不會錯過重要資訊。在需要處理歷史數據、解決訂閱時機問題、或實現資料快照功能時,ReplaySubject 是一個很好的選擇。

Last updated