Почему важно отписываться
Разберем какие проблемы могут возникнуть если забыть отписаться от 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-компоненты, и профайлер будет выглядеть так:
Это вольный перевод материала