为何未在Effect中调用Signal却触发了无限循环?
Angular Signal 无限循环问题分析
代码实现
export class Service { private http = inject(HttpClient) private injector = inject(Injector); // Hier wird der Injector geholt objectId = signal<string | null>(null); private _object = signal<IObject | null>(null); object = computed(() => this._object()); // Read-Only access // Fehler-Signal, falls die API fehlschlägt error = signal<string | null>(null); constructor() { effect(() => { const objectId = this.objectId() // this._object.set(null) this.error.set(null) if (objectId) { const secondId = 'secondId' console.log('id', objectId) this.getObjectById(objectId, secondId) return; } }, { allowSignalWrites: true }) } setObjectId(id: string | null) { this.objectId.set(id); } private getObjectById(objectId: string, secondId: string) { this.http.get<IObject>(this.objectUrl + `GetObjectById/${objectId}/${secondId}`).subscribe({ next: (object) => { this._object.set(object) }, error: () => { console.log('error') }, }) console.log(this.object()) // untracked(() => console.log(this.object())) } }
问题现象
当在getObjectById方法的subscribe前后调用console.log(this.object())时,会触发无限循环;但将日志放在subscribe回调内,或用untracked包裹日志语句则不会。注释掉this._object.set(null)后问题仍存在。
原因解析
核心:Effect的依赖追踪逻辑
- Effect会自动追踪其执行过程中所有被读取的信号,并将这些信号加入自身的依赖列表,当依赖信号更新时,Effect会重新执行。
- 你在
effect内部调用了getObjectById,而getObjectById里的console.log(this.object())会读取object这个computed信号——此时这个读取操作处于Effect的执行上下文内,所以object会被加入到当前Effect的依赖列表中。 - 当API请求完成,
subscribe的next回调执行this._object.set(object)时,_object信号更新,依赖它的objectcomputed信号也会随之更新。因为object已经在Effect的依赖列表里,Effect会被触发重新执行。 - Effect重新执行后,又会调用
getObjectById,再次读取object,再次触发Effect的依赖更新,最终形成无限循环。
两种解决方式的原理
- 将日志放在subscribe回调内:
subscribe的回调是异步执行的,此时已经脱离了Effect的执行上下文,读取object不会被记录到Effect的依赖列表中,所以_object更新时不会触发Effect重新执行。 - 用
untracked包裹日志:untracked的作用就是屏蔽当前的信号追踪上下文,内部的信号读取不会被加入到Effect的依赖列表,因此不会触发循环。
内容的提问来源于stack exchange,提问作者Lenard
相关产品推荐
相关产品推荐

