我正在尝试使用 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() 方法?
据我了解,这段代码有两个问题。首先,您不知道为什么控制台日志有重复。其次,即使您没有对行为主题调用“.next()”,您也不知道为什么视图会更新。
让我们从第一个开始。
您需要了解 rxjs observables 和 BehaviourSubjects 是如何工作的。 普通的可观察对象,当您订阅它时,将等待发出某个值,然后每次发生时,它都会调用您附加到它的操作。例如:
现在请注意,在这段代码中,我们仅在 ngOnInit 中订阅了一次。尽管如此,每次调用emitValue()方法(例如从按钮)时,console.log都会被调用。这是因为订阅会一直持续到取消订阅为止。这意味着,每次对主题调用 next() 时都会调用该操作。
那么当您订阅多次时会发生什么?让我们尝试一下:
我们订阅了一个主题 3 次,现在每当发出值时,控制台日志都会被调用 3 次。您创建的每个订阅都会调用它。
现在,当我们查看您的示例时,每次单击 addTask 按钮时都会添加订阅。这就是为什么每次添加任务时,都会多一个控制台日志。 但是,嘿,在您的第一个示例中,当您删除 .next() 时,您有一些控制台日志,即使您没有发出任何值,这是为什么呢?现在我们来到BehaviourSubject。这是一种特殊的主题,它拥有其价值,并在订阅后立即发出它。而且每次你调用 .next() 时它也会发出,但你没有这样做,所以这就是为什么它每次订阅时都会调用 1 个控制台日志。
你应该做的是打电话
仅一次,例如在 ngOnInit() 中
好了,现在我们进入第二期
这非常简单,但需要一些关于引用如何工作的知识。
在您的服务中,您将整个任务数组放入 BehaviourSubject 中。事实上,这个主题持有这个数组的引用。这意味着,每当您将新值推送到数组中时,BehaviourSubject 也会拥有它。这就是发生的情况,每当您调用 addTask 方法时,都会将新值推送到任务数组,并且您的 BehaviourSubject 也有它。