Почему важно отписываться
Разберем какие проблемы могут возникнуть если забыть отписаться от RxJS потока.
Для начала создадим реалистичный пример с использованием Angular и Akita.
Сначала создадим простое хранилище Akita, которое содержит одно свойство:
export interface DemoState {
someProp: number;
}
export function createInitialState(): DemoState {
return {
someProp: 0
};
}
@StoreConfig({ name: 'demo' })
export class DemoStore extends Store<DemoState> {
constructor() {
super(createInitialState());
}
}export class DemoQuery extends Query<DemoState> {
selectSomeProp$ = this.select(state => state.someProp);
constructor(protected store: DemoStore) {
super(store);
}
}Отлично. Теперь давайте создадим два компонента. Первый будет отслеживать ключ someProp хранилища, а второй компонент будет обновлять его.
export class PageOneComponent {
subscription: Subscription;
constructor(private demoQuery: DemoQuery) {}
ngOnInit() {
this.subscription = this.demoQuery.selectSomeProp$.subscribe(prop => {
this.someMethod();
});
}
someMethod() {}
}@Component({
template: `<button (click)="update()">Update prop</button>`
})
export class PageTwoComponent {
constructor(private demoService: DemoService) {}
update() {
this.demoService.updateKey();
}
}
1. Утечка памяти
Функция подписки сохраняет ссылку на this (экземпляр компонента), поэтому экземпляр PageOne недоступен для сборки мусора и не будет удален. В Chrome DevTools можно отследить проблему следующим образом:
Как видите, экземпляр компонента все еще находится в памяти, даже если Angular уничтожил компонент при переключении таба.
Проблема два — Неожиданное поведение
Предположим, нам нужно вызвать метод detectChanges() при обновлении someProp.
ngOnInit() {
this.subscription = this.demoQuery.selectSomeProp$.subscribe(prop => {
this.someMethod();
this.cdr.detectChanges();
});
}Теперь перейдем к компоненту PageTwoComponent, обновим хранилище и посмотрим, что происходит:
Мы не очистили нашу функцию подписки перед тем, как Angular уничтожил компонент, поэтому при обновлении хранилища она запускается и вызывает метод detectChanges() на компоненте, который с точки зрения Angular уже уничтожен.
Решение одно - делать отписки
ngOnDestroy() {
this.subscription.unsubscribe();
}начиная с Angular 16 был добавлен оператор takeUntilDestroy() который берет на себя большую часть работы связанной с отписками. Я писал про него ранее.
После отписки Javascript сможет удалять из памяти destroyed-компоненты, и профайлер будет выглядеть так:
Это вольный перевод материала