搜尋

首頁  >  問答  >  主體

當我不將新值傳遞給 next() 方法時,為什麼我的 BehaviourSubject 會更新?

我正在嘗試使用 RXJS 製作一個簡單的 TODO 應用程式。我有一個包含 TODO 任務的 JSON 伺服器模擬資料庫。

所以我最後得到了這個 TasksService:

@Injectable({
  providedIn: 'root'
})
export class TasksService
{
  private _tasks : ITask[] = [];
  private _tasks$: BehaviorSubject<ITask[]> = new BehaviorSubject<ITask[]>([]);
  constructor (private _http: HttpClient) { }

  public getTasks()
  {
    this.getTasksObservableFromDb().pipe(
      tap(
        (tasks) => 
        {
          this._tasks = tasks;
          this._tasks$.next(tasks);
        }
      )
    ).subscribe();

    return this._tasks$;
  }

  public addTask(task: ITask)
  {
    this._tasks.push(task);
    this._tasks$.next(this._tasks);
  }

  private getTasksObservableFromDb(): Observable<any>
  {
    return this._http.get<any>('http://127.0.0.1:3000/tasks');
  }

當我新增任務時,我不想立即將它們發佈到伺服器。 因此,當我從伺服器取得任務時,我將它們儲存到 _tasks 屬性,然後將它們傳遞給我的 _tasks$:BehaviorSubject 的 next() 方法。 因為後來我想將我的任務批次發佈到伺服器,現在我只想讓它們在 Angular 中正確顯示。

在我的 AppComponent 中,我取得任務並將它們指派給我的任務屬性。

export class AppComponent implements OnInit
{
  public tasks!:BehaviorSubject<ITask[]>;
  constructor (private _tasksService: TasksService)
  {}
  ngOnInit(): void
  {
    console.log('OnInit');
    this.tasks = this._tasksService.getTasks();
  }

  public addTask()
  {
    this._tasksService.addTask(
      {
        id: crypto.randomUUID(),
        isImportant: true,
        text: 'Added task'
      }
    );
  }
}

在我的 HTML 範本中,我使用非同步管道作為我的任務屬性並顯示我的任務:

<ng-container *ngFor="let task of tasks | async">
    {{task.text}}
    {{task.id}}
</ng-container>
<button type="button" (click)="addTask()">Add Task</button>

但後來我不小心刪除了我的 TaskService 中的這一行: this._tasks$.next(this._tasks);

所以我的方法現在看起來像這樣:

public addTask(task: ITask)
  {
    this._tasks.push(task);
  }

但是新增任務仍然有效!即使我沒有為我的BehaviorSubject 傳遞新任務數組,Angular 也會顯示新新增的任務。

所以我決定記錄我的任務中的值! :我的 AppComponent 類別中的BehaviorSubject<ITask[]> 屬性:

public addTask()
  {
    this._tasksService.addTask(
      {
        id: crypto.randomUUID(),
        isImportant: true,
        text: 'Added task'
      }
    );

    this.tasks.pipe(tap((value) => console.log(value)
    )).subscribe();
  }

且任務會如預期新增 - 每次取得包含一個任務的陣列時:

Array(3) [ {…}, {…}, {…} ] <- Add task button is clicked 
Array(4) [ {…}, {…}, {…}, {…} ] <- Add task button is clicked 
Array(5) [ {…}, {…}, {…}, {…}, {…} ] <- Add task button is clicked

但是當我將這一行傳回 TaskService 中的 addTask 方法時: this._tasks$.next(this._tasks);

我得到這些日誌:

Array(3) [ {…}, {…}, {…} ] <- Add task button is clicked -> one task is added
Array(4) [ {…}, {…}, {…}, {…} ] <- Add task button is clicked -> one task is added
Array(4) [ {…}, {…}, {…}, {…} ] <- I get the same array 
Array(5) [ {…}, {…}, {…}, {…}, {…} ] <- Add task button is clicked -> one task is added
Array(5) [ {…}, {…}, {…}, {…}, {…} ] <- I get the same array 
Array(5) [ {…}, {…}, {…}, {…}, {…} ] <- I get the same array once again

所以我有點迷失為什麼可觀察的行為是這樣的......也許我不完全理解 next() 方法?

P粉321584263P粉321584263254 天前421

全部回覆(1)我來回復

  • P粉080643975

    P粉0806439752024-03-22 00:14:34

    據我了解,這段程式碼有兩個問題。首先,您不知道為什麼控制台日誌有重複。其次,即使您沒有對行為主題呼叫“.next()”,您也不知道為什麼視圖會更新。

    讓我們從第一個開始。

    您需要了解 rxjs observables 和 BehaviourSubjects 是如何運作的。 普通的可觀察對象,當您訂閱它時,將等待發出某個值,然後每次發生時,它都會呼叫您附加到它的操作。例如:

    exampleSubject = new Subject();
    
    ngOnInit(): void {
        this.exampleSubject.pipe(
            tap(console.log)
        ).subscribe();
    }
    
    emitValue() {
        this.exampleSubject.next("text");
    }

    現在請注意,在這段程式碼中,我們僅在 ngOnInit 中訂閱了一次。儘管如此,每次呼叫emitValue()方法(例如從按鈕)時,console.log都會被呼叫。這是因為訂閱會持續到取消訂閱為止。這意味著,每次對主題呼叫 next() 時都會呼叫該操作。

    那麼當您訂閱多次時會發生什麼?讓我們試試看:

    exampleSubject = new Subject();
    
    ngOnInit(): void {
        subscribeToSubject(); // 1st
        subscribeToSubject(); // 2nd
        subscribeToSubject(); // 3rd
        this.emitValue();
    }
    
    emitValue() {
        this.exampleSubject.next("text");
    }
    
    subscribeToSubject() {
        this.exampleSubject.pipe(
            tap(console.log)
        ).subscribe();
    }

    我們訂閱了一個主題 3 次,現在每當發出值時,控制台日誌都會被呼叫 3 次。您創建的每個訂閱都會呼叫它。

    現在,當我們查看您的範例時,每次點擊 addTask 按鈕時都會新增訂閱。這就是為什麼每次新增任務時,都會多一個控制台日誌。 但是,嘿,在您的第一個範例中,當您刪除 .next() 時,您有一些控制台日誌,即使您沒有發出任何值,這是為什麼呢?現在我們來到BehaviourSubject。這是一種特殊的主題,它擁有其價值,並在訂閱後立即發出它。而且每次你​​調用 .next() 時它也會發出,但你沒​​有這樣做,所以這就是為什麼它每次訂閱時都會調用 1 個控制台日誌。

    你應該做的是打電話

    this.tasks.pipe(tap((value) => console.log(value)
    )).subscribe();

    僅一次,例如在 ngOnInit() 中

    好了,現在我們進入第二期

    這非常簡單,但需要一些關於引用如何運作的知識。

    在您的服務中,您將整個任務陣列放入 BehaviourSubject 中。事實上,這個主題持有這個陣列的引用。這意味著,每當您將新值推送到陣列中時,BehaviourSubject 也會擁有它。這就是發生的情況,每當您呼叫 addTask 方法時,都會將新值推送到任務數組,並且您的 BehaviourSubject 也有它。

    回覆
    0
  • 取消回覆