Компонент - это основной строительный блок приложения Angular. При работе с большим приложением очень важно иметь общие компоненты для общих функций. Ранее я написал статью о четырех практических советах по созданию хорошего общего компонента:
Здесь я собираюсь дать еще четыре совета по повышению качества компонентов.
5. Уничтожить входные данные объекта.
Обертывание нескольких аргументов в объект - это практика классического JavaScript. Тем не менее, это антипаттерн для компонента Angular.
export interface Configuration { enableFilter: boolean; enableSort: boolean; ... } @Component({ selector: 'custom-grid', ... }) export class CustomGridComponent { @Input() configuration: Configuration; ... }
Основная проблема заключается в том, что это создает дополнительную сложность и требует поддержки кода. Это также затрудняет установку значения по умолчанию. Кроме того, его неудобно использовать, поскольку разработчикам приходится объявлять дополнительные переменные в своих представлениях.
@Component({ template: ` <custom-grid [configuration]="gridConfiguration"></custom-grid> `, ... }) export class TestComponent { gridConfiguration: Configuration = { enableFilter: true, enableSort: true } ... }
Настоятельно рекомендуется всегда стараться деструктурировать входные данные объекта как можно более плоскими.
@Component({ selector: 'custom-grid', ... }) export class CustomGridComponent { @Input() enableFilter: boolean; @Input() enableSort: boolean; ... }
Теперь разработчики могут:
<custom-grid [enableFilter]="true" [enableSort]="true"></custom-grid>
Это не только делает его намного проще, но также позволяет нам указывать значение по умолчанию для каждого атрибута отдельно, как то, что я упоминал ранее в советах №1.
6. Замените входную мутацию на выходную привязку.
Если вы ненавидите компанию, в которой работаете, и хотите облажаться перед уходом, одна из лучших ловушек, которую вы можете сделать, - это обновить связанный объект @Input()
:
@Component({ selector: 'app-parent', template: ` <div>Parent: {{ counterState | json }}</div> <app-child [state]="counterState"></app-child> ` }) export class ParentComponent { counterState = { counter: 0 }; } @Component({ selector: 'app-child', template: ` <div>Child: {{ state | json }}</div> <button (click)="onUpdateCounter()">Update Counter</button> ` }) export class ChildComponent { @Input() state: { counter: number }; onUpdateCounter() { this.state.counter++; } }
Проблема обновления связанного объекта заключается в том, что это также повлияет на родительский объект. Однако нет никакой информации для других разработчиков, чтобы понять такое поведение при использовании компонента. Это может очень легко вызвать проблему, а также очень сложно отладить, поскольку логика обновления скрыта внутри компонента. Разработчики должны копаться в компонентах, которые они использовали, один за другим, прежде чем они наконец смогут узнать, где происходит обновление. Лучший подход - всегда использовать двустороннюю привязку всякий раз, когда есть обновление, которое выходит из компонента:
export interface CounterState { counter: number; } @Component({ selector: 'app-parent', template: ` <div>Parent: {{ counterState | json }}</div> <app-child [(state)]="counterState"></app-child> ` }) export class ParentComponent { counterState: CounterState = { counter: 0 }; } @Component({ selector: 'app-child', template: ` <div>Child: {{ state | json }}</div> <button (click)="onUpdateCounter()">Update Counter</button> ` }) export class ChildComponent { @Input() state: CounterState; @Output() stateChange = new EventEmitter<CounterState>(); onUpdateCounter() { this.stateChange.emit({ ...this.state, counter: this.state.counter + 1 }); } }
Для других разработчиков гораздо яснее, что компонент обновит связанную переменную, поскольку теперь это явно двусторонняя привязка (он заблокирует обновление, если они попытаются выполнить одностороннюю привязку с <app-child [state]=”counterState”></app-child>
).
Кстати, гораздо безопаснее обновлять примитивный ввод, поскольку он не будет отражен в родительском элементе (@Input counter: number;
). Тем не менее, по-прежнему настоятельно рекомендуется использовать двустороннюю привязку для всех случаев входной мутации для обеспечения согласованности данных и удобства обслуживания.
7. Извлечь вызовы API в компонент оболочки.
Хотя это не официальная практика, по моему опыту, она весьма полезна для улучшения возможности повторного использования компонентов. При работе с компонентами более высокого уровня с большей бизнес-логикой внутри обычно есть вызовы API:
@Component({ template: ` <div class="grid"> <div class="row"> <div *ngFor="let product of products" class="col-6 col-md-4 mb-2"> <div class="card bg-white" (click)="navigateTo(product)"> <div class="card-header"> <img [src]="product.img"> </div> <div class="card-body"> <strong>{{product.name}}</strong> <div>Brand: {{product.brand}}</div> <div>Color: {{product.color}}</div> <div>Style: {{product.style}}</div> </div> <div class="card-footer text-center"> <span class="price">${{product.price}}</span> </div> </div> </div> </div> </div> `, ... }) export class ProductOverviewComponent { products: Product[] = []; constructor(productService: ProductService) { productService.getAllProducts() .subscribe(products => this.products = products); } ... }
Мы можем извлечь вызовы API в компонент-оболочку, чтобы внутренний компонент можно было сохранить презентационным.
@Component({ selector: 'product-grid', template: ` <div class="grid"> <div class="row"> <div *ngFor="let product of products" class="col-6 col-md-4 mb-2"> <div class="card bg-white" (click)="navigateTo(product)"> <div class="card-header"> <img [src]="product.img"> </div> <div class="card-body"> <strong>{{product.name}}</strong> <div>Brand: {{product.brand}}</div> <div>Color: {{product.color}}</div> <div>Style: {{product.style}}</div> </div> <div class="card-footer text-center"> <span class="price">${{product.price}}</span> </div> </div> </div> </div> </div> `, ... }) export class ProductGridComponent { @Input() products: Product[] = []; ... } @Component({ template: ` <product-grid [products]="products"></product-grid> `, ... }) export class ProductOverviewComponent { products: Product[] = []; constructor(productService: ProductService) { productService.getAllProducts() .subscribe(products => this.products = products); } }
Основное преимущество презентационных компонентов состоит в том, что они облегчают повторное использование кода. Намного проще создать новый компонент поверх существующего презентационного компонента, поскольку нам не нужно обрабатывать скрытую внутреннюю логику. Тем не менее, этот подход имеет недостаток в виде большего количества кода и, следовательно, большей сложности. Следовательно, рекомендуется использовать его только в больших проектах, в которых несколько разработчиков работают над пулом компонентов.
Кстати, если вы хотите узнать больше о повторном использовании компонентов, здесь я написал еще одну статью о методах повторного использования существующего компонента:
8. Используйте подход одного файла для простых компонентов
Не все компоненты сложны. Есть несколько простых компонентов, в которых всего несколько строк кода и стилей. Рекомендуется использовать однофайловый подход для таких компонентов:
@Component({ template: ` <div class="card"> <div class="card-header"> {{displayText}} </div> </div> `, styles: [` .card-header { color: green; } `] }) export class CardComponent { ... }
Это делается для того, чтобы сделать его более удобным для разработчиков, поскольку разработчики могут просто сохранить отдельный файл в разделенном виде во время разработки без необходимости переключаться между ними.
Спасибо за прочтение! Надеюсь, эта статья окажется для вас полезной. Будем очень признательны за любые комментарии. : D