От автора: есть много способов реализовать связь между компонентами. Один из наиболее часто используемых подходов — вызовы публичных методов. Многие разработчики с сильным опытом классической веб-разработки (например, JQuery) любят использовать этот подход для разработки компонентов. Однако на самом деле это не очень хорошая практика в мире Angular.
Проблема 1. Он не может использовать управление изменениями Angular
Одна из самых мощных функций Angular — автоматическое управление состояниями. Когда состояния в компоненте обновляются, Angular помогает автоматически отражать их в шаблоне посредством обнаружения изменений.
1 2 3 4 5 6 |
@Component({ template: `{{value}}` }) export class TestComponent { value = 1; } |
Например, когда value в классе обновляется, Angular автоматически обновит value в шаблоне. Однако мы не сможем применить эту функцию при использовании компонентов, которые полагаются на публичные методы. Это я хотел бы пояснить на примере:
1 2 3 4 5 6 7 8 9 |
@Component({ selector: 'custom-button', ... }) export class CustomButtonComponent { activated = false; public setActivated(value: boolean) { this.activated = value; } } |
Для этого типа компонентов нам нужно будет сохранить ссылку, чтобы получить доступ к его публичным методам.
1 2 3 4 5 6 7 8 |
@Component({ template: `<custom-button #customButton></custom-button>` }) export class PageComponent { @ViewChild('customButton') customButton: CustomButtonComponent; doSomething() { this.customButton.setActivated(true); } } |
Теперь предположим, что у нас есть еще один компонент <filter>, у которого есть атрибут activeFilter. Мы хотим, чтобы кнопка активировалась, когда activeFilter не пуста.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
@Component({ template: ` <filter [activeFilter]="activeFilter" (activeFilterChange)="onActiveFilterChange($event)"></filter> <custom-button #customButton></custom-button> ` }) export class PageComponent { @ViewChild('customButton') customButton: CustomButtonComponent; activeFilter = {}; onActiveFilterChange(activeFilter) { this.activeFilter = activeFilter; this.customButton.setActivated(activeFilter.length > 0); } } |
Основная проблема заключается в том, что мы сами должны управлять изменением состояния. В этом случае мы должны вызывать setActivated() вручную всякий раз, когда знаем, что состояние изменилось. Это означает, что мы должны найти все триггерные точки изменения состояния и поместить их setActivated() туда. Это будет кошмар для более крупных компонентов с более сложной бизнес-логикой.
Переработав компонент с использованием подхода привязки, мы можем сделать код намного чище:
1 2 3 4 5 6 7 |
@Component({ selector: 'custom-button', ... }) export class CustomButtonComponent { @Input() activated = false; } |
Теперь мы можем сделать:
1 2 3 4 5 6 7 8 9 10 |
@Component({ template: ` <filter [(activeFilter)]="activeFilter"></filter> <custom-button [activated]="activeFilter.length > 0"></custom-button> ` }) export class PageComponent { activeFilter = {}; } |
Мутация состояния может быть связана через различные компоненты с помощью управления состоянием Angular, что поможет уменьшить количество ненужного кода. Самое главное, нам больше не нужно заботиться о том, какая часть кода может вызвать изменение данных. Angular может помочь нам гарантировать, что каждый раз при обновлении activeFilter изменение будет отражено в шаблоне. Это значительно снижает сложность и расходы на обслуживание.
Проблема 2: Это не подходит для наследования компонентов
Следующая проблема касается повторного использования компонентов. Тем не менее, это связано не с самим компонентом, а с его родителем, который его использует. Опять же, я хотел бы пояснить это на примере:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
@Component({ template: ` <div>{{data | json}}</div> <custom-button #customButton></custom-button> ` }) export class PageComponent implements OnInit { @ViewChild('customButton') customButton: CustomButtonComponent; data = []; constructor( private testService: TestService ) {} ngOnInit() { this.testService.getData() .then(data => { this.customButton.setActivated(data.length > 0); this.data = data; }) } } |
Предположим, у нас есть инструмент TestService, который помогает получать данные. Мы хотим, чтобы кнопка активировалась, когда полученные данные не пусты. Проблема здесь в том, что родительский элемент должен поддерживать ссылку на элемент, чтобы вызывать общедоступные методы. Это создает связь между шаблоном и классом, что вызывает множество проблем для других разработчиков, которые пытаются повторно использовать этот компонент посредством наследования.
1 2 3 4 5 6 7 |
@Component({ template: ` <div>{{data | json}}</div> <div>Hi, I am a special page without button.</div> ` }) export class SpecialPageComponent extends PageComponent {} |
Когда разработчики хотят повторно использовать эти коды посредством наследования, они должны обеспечить существование элемента #customButton, даже если он им не нужен. SpecialPageComponent выше будет просто выводить ошибку нуля.
Мы можем обойти это ограничение, используя привязку:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
@Component({ template: ` <div>{{data | json}}</div> <custom-button [activated]="data.length > 0"></custom-button> ` }) export class PageComponent implements OnInit { data = []; constructor( private testService: TestService ) {} ngOnInit() { this.testService.getData() .then(data => { this.data = data; }) } } |
Теперь логика придерживается шаблоном без какой-либо связи с классом. Разработчики могут более гибко повторно использовать код без необходимости изменять дизайн шаблона.
Заключение
Публичные методы удобны в использовании, но они также вызывают другие проблемы. В идеале при разработке компонента следует избегать использования публичных методов, особенно для назначений, которые можно легко заменить привязками. Для публичных методов без назначения мы должны стараться избегать их на более высоком уровне, например, используя проекцию контента, чтобы избежать кросс-компонентной логики.
Автор: Liu Ting Chun
Источник: //medium.com
Редакция: Команда webformyself.