【问题标题】:Circular call to cause stack size exceed导致超出堆栈大小的循环调用
【发布时间】:2020-05-15 19:08:59
【问题描述】:

我有两个同级组件,我想在它们之间共享数据。为了用户的方便,如果组件一上的 UI 发生了变化,那么我想发出通知组件二。那么组件二的 UI 应该会被传入的参数改变。

反之亦然,如果组件二的 UI 发生变化,我也想通知组件一。所以我使用 Behavior subject 来共享数据。

但是它会导致循环调用。

Stackblize BehaviorSubject demo

BehaviorSubject 在服务类中。

import { Injectable} from '@angular/core';
import {BehaviorSubject, Observable} from 'rxjs';

@Injectable()
export class UsersService{
  constructor(){}
  private user = new BehaviorSubject<number>(0);

  public getUser = (): Observable<number> => {
   return this.user.asObservable();
 }
  public editUser = (newUser: number) => {
   this.user.next(newUser); 
 }

}

所以我们有 get 和 set 部分。在兄弟组件中,我们调用了ngOninit中的get user part。

 ngOnInit(){

 this.userService.getUser().subscribe(u => {
    if(u > 0) {
       this.patchData(u);
       this.newUser = u + 1;
       console.log(u);
      }
    });
  }

一旦我们得到用户编号,我们就会发出,以便其他组件可以接收它并在之后呈现 UI。

错误显示在控制台中。

【问题讨论】:

    标签: angular rxjs


    【解决方案1】:

    我认为问题出在架构上。在这种情况下,每个组件都存储自己的状态,其他组件也依赖于该状态。我认为最好的方法是拥有单一事实来源,并让该来源更新状态并将其分派给消费者,消费者可以从中选择切片。

    例如,如果你想将用户状态保存在一个地方,你可以像这样修改你的UserService

    class UserService {
      private userSource = new Subject<Partial<User>>();
    
      user$ = this.userSource.asObservable();
    
      constructor () {
        this.user$ = this.userSource.pipe(
          // It's important that we return a new reference(e.g for the async pipe)
          scan((acc, crt) => ({ ...acc, ...crt }))
        )
      }
    
      addUser (u: User) {
        this.userSource.next(u);
      }
    }
    

    现在您可以拥有N 消费者。我认为这种方法还解决了另一个问题,即您将更新特定(内部)状态的逻辑和为其他消费者分发状态的逻辑混合在一起。例如

    this.patchData(u); // Notify other consumers
    this.newUser = u + 1; // Update inner state
    

    数据消费者(例如组件)如下所示:

    class DataConsumer {
      get user$ () {
        return this.userService.user$;
      }
    
      constructor (private userService: UserService) { }
    
      // Updating the user only when needed(e.g user input)
      changeUser (name) {
        this.userService.addUser({ name });
      }
    }
    

    和视图:

    {{ (user$ | async) as user }}
    

    【讨论】:

    • 我想也许我可以创建一个静态变量来存储值。然后在自定义组件的 ngOnInit 中检索它,并在操作部分更改它。你觉得这样好吗?
    • 为什么它必须是静态的?
    • 反正不是静态的就可以了。但是我可以用一个变量来存储它吗?
    • 你的意思是一个变量,比如,而不是一个可观察的?如果您需要 直接 访问 observable 的当前状态,则可以改用 BehaviorSubject
    • 是的,我只想要一个数字。该值可以在组件之间共享。
    【解决方案2】:

    我用了另一种方式。可能Get current value from Observable without subscribing (just want value one time)

    在数据服务中,我只返回当前值而不是使用订阅。

    export class UsersService{
    constructor(){}
    private user = new BehaviorSubject<number>(0);
    
    public getUser = () => {
       return this.user.value;
    }
    public editUser = (newUser: number) => {
       this.user.next(newUser); 
    }
    

    【讨论】:

      【解决方案3】:

      你的逻辑确实是循环的!

      我认为您缺少的要点是,服务是您的 user 值的唯一真实来源。当它发生变化时,订阅者会自动收到通知,因为最新的值被推送到从 getUser() 方法创建的可观察订阅中。

      无需两个组件再次将更新推送到服务。组件 #1 可以简单地调用 userService.editUser() 方法,这会导致服务更新其内部状态,从而自动将新值推送给订阅者。

      我已经在这个stackblitz 中为你工作了。

      另外,您会注意到我在模板中使用了async pipe,而不是在控制器中订阅。有many benefits 这样做,其中之一是更简单的代码:-)

      【讨论】:

      • 您的代码假设我更改了组件一然后通知组件二。有用。假设我在组件二中有按钮,如果我单击它,那么我也想通知组件一。怎么样?
      • 没问题。你只需调用userService.editUser() 方法。
      • 它的工作方式是,1)组件订阅服务的公共可观察属性user$。 2)当组件想要更新值时,它们会调用内部更新该状态的服务方法。 3) 组件本身会自动收到新值通知,因为它们已被订阅。
      • 能否将其他同级组件中的数字类型值赋给组件中的新变量?我想查看结果或记录它。非常感谢。
      • 我不明白你在问什么:“分配号码类型”。要记录通过可观察流发出的结果,您可以在用户服务中将.pipe(map()) 添加到public user$ = this.user.asObservable().pipe(map(console.log)) 的分配上。这有帮助吗?
      猜你喜欢
      • 2021-05-20
      • 1970-01-01
      • 2023-01-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-11-17
      • 2019-09-09
      • 1970-01-01
      相关资源
      最近更新 更多