搜索

首页  >  问答  >  正文

当我不将新值传递给 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粉321584263252 天前418

全部回复(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
  • 取消回复