각도가 있는 구성 요소 재사용 기술

구성 요소 간 공유 논리적 기술


본고는 최초Bits and Pieces에 의해 Giancarlo Buomprisco에 발표되었다
DRY는 소프트웨어 공학의 기본 개념 중의 하나이다.소프트웨어 엔지니어로서 우리는 항상 가능한 한 적은 코드를 사용하여 가능한 한 많은 코드를 구축하려고 노력한다.
이것은 확실히 좋은 일이다. 왜냐하면 우리가 더 적은 코드를 발표하고 생산력을 높이며 건강한 코드 라이브러리를 유지할 수 있기 때문이다.
본고는 가능한 한 많은 코드를 공유함으로써 Angular 구성 요소의 사용 가능한 기술을 소개하고자 합니다.
  • 클래스 상속
  • 류 혼합물
  • 성분 구성
  • 팁: BitGithub를 사용하면 프로젝트에서 각도 구성 요소를 쉽게 공유하고 다시 사용할 수 있으며, 업데이트, 동기화, 변경을 권장하며, 팀으로서 더욱 빨리 구축할 수 있습니다.
    Bit 각도 구성 요소: 하나의 팀으로서 프로젝트 간에 쉽게 공유

    구성 요소 클래스 상속


    저는 가장 싫지만 각도 구성 요소 간에 코드를 공유하는 가장 일반적인 방법은 ES6 클래스 계승 * extends 키워드 사용 *
    여러 가지 이유로 ES6 클래스의 계승은 자바스크립트 커뮤니티에서 매우 논쟁적인 것으로 여겨지지만 여전히 각진 세계에서 믿을 수 없이 사용된다.정확한 방식으로 사용되고 남용되지 않을 때, 이 기술은 구성 요소 간에 코드를 공유하는 좋은 해결 방안이다.
    구성 요소 ListComponent를 만들어서 계승적인 구성 요소를 확장하는 예시를 보겠습니다. 이 구성 요소는 다른 두 개의 확장 기본 기능의 추상적인 클래스로 확장되고 실제 각도의 구성 요소로 추상적인 클래스를 실현합니다.
    이런 기술을 사용하는 흔히 볼 수 있는 방법은 추상적인 클래스를 만들고 그곳에서 모든 하위 클래스를 공유하는 방법을 정의하는 것이다.하위 클래스는 반드시 추상적인 방법을 실현하거나 기존의 방법을 다시 써야 할 수도 있다.

    ListComponent 베이스 클래스


    기본 클래스는 매우 간단합니다. 우리는 입력 항목만 정의합니다.
        export abstract class ListComponent {
          @Input() items: Item[];
        }
    
    다음으로, 우리는 페이지를 나누고 선택을 통해 간단한 목록을 확장하는 기능을 원한다.따라서 우리는 다른 두 개의 추상적인 클래스 확장기 목록을 계속 사용한다.

    PageableListComponent 페이지 목록 구성 요소


    구성 요소 PageableListComponent는 ListComponent를 확장하고 페이지 나누기 기능을 추가합니다.
        export abstract class PageableListComponent extends ListComponent {
            page = 0;
            itemsPerPage = 2;
    
            get start() {
             return this.page * this.itemsPerPage;
            }
    
            get end() {
             return this.page * this.itemsPerPage + this.itemsPerPage;
            }
    
            get pages() {
              return new Array(this.items.length / this.itemsPerPage);
            }
    
            changePage(page: number) {
              this.page = page;
            }
        }
    

    목록 구성 요소 선택 가능


    구성 요소 SelectableListComponent는 PageableListComponent를 확장하고 선택/선택 취소 기능을 추가합니다.
        export abstract class SelectableListComponent extends PageableListComponent {
          @Output() selected = new EventEmitter<Item>();
          @Output() unselected = new EventEmitter<Item>();
    
          selectedItems: Item[] = [];
    
          select(item: Item) {
            this.selected.emit(item);
            this.selectedItems = [...this.selectedItems, item];
          }
    
          unselect(item: Item) {
            this.unselected.emit(item);
            this.selectedItems = this.selectedItems.filter(({value}) => value !== item.value);
          }
    
          isItemSelected(item: Item) {
            return this.selectedItems.some(({value}) => item.value === value);
          }
        }
    

    구현 구성 요소: CustomerListComponent


    마지막으로, 우리는 CustomerListComponent 클래스의 실현을 만들었고, 이를 확장하여 SelectableListComponent를 확장했다.템플릿과 구성 요소는 우리가 다른 클래스에서 지정한 모든 출력과 입력에 접근할 수 있습니다.
        @Component({
          selector: 'customers-list',
          template: `
            <div *ngFor="let item of items | slice: start : end">
             <label>
               <input
                 type="checkbox"
                 [checked]="isItemSelected(item)"
                 (change)="
                   $event.target.checked ? select(item) : unselect(item)
                 "
               />
              {{ item.display }}
             </label>
            </div>
            <div class='pages'>
            <div *ngFor="let p of pages; let i = index;" 
                 class='page' 
                 [class.selected]="i === page" 
                 (click)="changePage(i)"
             >
             {{ i }}
            </div>
          </div>
        `
        })
        export class CustomersListComponent extends SelectableListComponent {}
    
        // USAGE
        <customers-list [items]="customers" 
                        (selected)="onSelected($event)"               
                        (unselected)="onUnselected($event)"
        ></customers-list>
    
    장식기의 메타데이터가 다시 정의되어야 함에도 불구하고, CustomerListComponent에서 하위 클래스를 만들 수 있습니다.이것은 우리가 새로운 구성 요소에 새로운 선택기, 템플릿, 스타일 등을 분배해야 한다는 것을 의미한다.다시 사용하려면 URL을 상위 클래스에 지정할 수 있습니다.
        @Component({
          selector: 'new-customers-list',
          templateUrl: '../customers-list/customers-list.component.html'
        })
        export class NewCustomersListComponent extends CustomersListComponent {}
    

    어셈블리 클래스 블렌드


    각도 구성 요소 클래스 간에 논리를 공유하기 위해서, 우리는 사람들에게 잘 알려지지 않은 방법, 즉mixin을 이용할 수 있다.mixin은 여러 개의 확장 목표 클래스의 작은 클래스를 조합할 수 있지만, 다중 계승을 사용할 필요가 없습니다.

    Typescript Mixin 예


    간단한 예로mixin이 무엇인지 보여 드리겠습니다.먼저 기본 클래스를 정의합니다.
        class BaseButton {
          label: string;
          disabled: boolean;
        }
    
    다음으로, 우리는 새로운 미니 클래스로 기본 클래스를 확장하는 함수를 정의합니다
        function themeMixin(BaseClass) {
            return class extends BaseClass {
              theme: string;
            }
        }
    
    마지막으로 Mixin을 사용하여 BaseButton 클래스를 확장했습니다.
        class PrimaryButton extends themeMixin(BaseButton) {}
    

    mixin을 사용하여 CustomerListComponent 구축


    icin을 사용하여 CustomerListComponent 예제를 다시 작성합니다.
        export function pageableListMixin(BaseClass) {
          return class extends BaseClass {
            page = 0;
            itemsPerPage = 2;
    
            get pages() {
              return new Array(this.items.length / this.itemsPerPage);
            }
    
            changePage(page: number) {
              this.page = page;
            }
    
            get start() {
             return this.page * this.itemsPerPage;
            }
    
            get end() {
             return this.page * this.itemsPerPage + this.itemsPerPage;
            }
        }
    
        export function selectableListMixin(BaseClass) {
          class SelectableListMixin extends BaseClass {
            @Output() selected = new EventEmitter<Item>();
            @Output() unselected = new EventEmitter<Item>();
    
            selectedItems: Item[] = [];
    
            select(item: Item) {
              this.selected.emit(item);
              this.selectedItems = [...this.selectedItems, item];
            }
    
            unselect(item: Item) {
              this.unselected.emit(item);
              this.selectedItems = this.selectedItems.filter(({value}) => {
                return value !== item.value;
              });
            }
    
            isItemSelected(item: Item) {
              return this.selectedItems.some(({value}) => {
                return item.value === value;
              });
            }
          }
    
          return SelectableListMixin;
        }
    
    구성 요소를 구성하는 데 필요한 모든mixin을 정의한 후,mixin을 가져오고 기본 클래스를 매개 변수로 전달합니다.
    그리고 우리는 간단하게mixin CustomerListMixin으로 CustomerListComponent를 확장합니다.
        const CustomersListMixin = 
          selectableListMixin(
            pageableListMixin(ListComponent)
          );
    
        @Component(...)
        export class CustomersListComponent extends CustomersListMixin {}
    
    비록 Mixin도 약간의 결함이 있지만 내가 보기에 적어도 장기적으로 보면 이것은 더욱 우아하고 안전한 다중 계승 해결 방안이다.

    성분 구성


    구성 요소 조합은 계승과 혼합을 보충하는 기술이다. 우리는 더 많은 기능을 가진 구성 요소를 확장하는 것이 아니라 여러 개의 작은 구성 요소를 조합하여 같은 결과를 실현할 수 있다.

    ListComponent:ngTemplateOutlet의 힘을 이용하여


    우리가 만들 수 있는 첫 번째 구성 요소는 공통적이고 다시 사용할 수 있는 구성 요소 ListComponent입니다. 이 구성 요소의 직책은 부모 구성 요소가 제공하는 시작과 끝 인덱스에 따라 프로젝트를 간단하게 보여주는 것입니다.
    보시다시피, 이 구성 요소는 모든 단독 항목을 표시하는 방법을 지정하지 않습니다. 부모 레벨은ngTemplateOutlet을 제공하고 모든 항목을 상하문 전달로 정의합니다.
        @Component({
          selector: "list",
          template: `
            <div *ngFor="let item of items | slice : start : end">
              <ng-container 
               *ngTemplateOutlet="template; context: { item: item }"
              >
              </ng-container>
            </div>
        `
        })
        export class ListComponent {
          @Input() items: Item[] = [];
          @Input() itemsPerPage = 2;
          @Input() currentPage: number;
    
          @ContentChild('item', { static: false }) 
          template: TemplateRef<any>;
    
          get start() {
            return this.currentPage * this.itemsPerPage;
          }
    
          get end() {
            return this.currentPage * this.itemsPerPage + this.itemsPerPage;
           }
        }
    

    페이지 구성 요소


    그리고 페이지 번호를 표시하고 사용자가 페이지를 클릭할 때 학부모에게 알리는 페이지 구성 요소를 추가했습니다.
        @Component({
          selector: "pagination",
          template: `
            <div class="pages">
              <div
               *ngFor="let p of pages; let i = index"
               class="page"
               [class.selected]="i === currentPage
               (click)="pageChanged.emit(i)"
              >{{ i }}
              </div>
           </div>
        `
        })
        export class PaginationComponent {
          @Input() currentPage: number;
          @Input() itemsPerPage = 2;
          @Input() itemsLength: number;
    
          @Output() pageChanged = new EventEmitter<number>();
    
          get pages() {
            return new Array(this.itemsLength / this.itemsPerPage);
          }
        }
    

    고객 구성 요소


    다음은 목록의 모든 항목을 표시하는 구성 요소를 정의합니다. 이 구성 요소는 항목의 표시 방식을 정의하고 항목을 선택하거나 선택 취소할 때 이벤트를 스케줄링합니다.
        @Component({
          selector: "customer",
          template: ` 
            <label>
              <input
                type="checkbox"
                [checked]="isSelected"
                (change)="$event.target.checked ? selected.emit(item) : unselected.emit(item)"
              />
              {{ item.display }}
           </label>
        `
        })
        export class CustomerComponent {
          @Input() item: Item;
          @Input() isSelected: boolean;
    
          @Output() selected = new EventEmitter<Item>();
          @Output() unselected = new EventEmitter<Item>();
        }
    

    CustomerListComponent 고객 목록 구성 요소


    이제 일을 한데 모을 때가 됐어!우리는 이전에 정의된 구성 요소를 다시 사용해서 고객 목록을 구성할 수 있습니다. 이 목록은 선택할 수 있고 페이지를 나눌 수 있습니다.이 구성 요소들은 모두 다시 사용할 수 있으며, 다른 목록과 구성할 수 있다.
        @Component({
          selector: "composition-customers-list",
          template: `
            <list
             [items]="items"
             [itemsPerPage]="2"
             [currentPage]="currentPage"
            >
             <ng-template #item let-item="item">
               <customer
                (selected)="selected($event)"
                (unselected)="unselected($event)"
                [item]="item"
                [isSelected]="isItemSelected(item)"
               ></customer>
             </ng-template>
            </list>
    
            <pagination
             [currentPage]="currentPage"
             [itemsLength]="items.length"
             [itemsPerPage]="2"
             (pageChanged)="currentPage = $event"
            ></pagination>
        `
        })
        export class CompositionCustomersListComponent {
          @Input() items = [];
    
          currentPage = 0;
          selectedItems = [];
    
          selected(item) {
            this.selectedItems = [...this.selectedItems, item];
          }
    
          unselected(item) {
            this.selectedItems = this.selectedItems.filter(({ value }) => value !== item.value);
          }
    
          isItemSelected(item) {
            return this.selectedItems.some(({ value }) => item.value === value);
          }
        }
    
    구성 요소 조합은 고도의 재사용, 깨끗하고 효과적인 구성 요소를 만드는 최종 방식이자 내가 가장 좋아하는 사고 코드 공유와 재사용성 방식이다.
    God 구성 요소를 작성하는 것이 아니라 많은 작은 구성 요소를 다시 사용할 수 있습니다.모든 구성 요소의 공공 API를 정확하게 사용하는 것은 응용 프로그램의 나머지 부분과 잘 협력하는 데 매우 중요하다.
    위에서 보듯이 우리는 여전히 중복된 논리를 가지고 있다. 왜냐하면 우리는 모든 목록을 위해 몇 가지 방법을 다시 썼기 때문이다. 이것이 바로 기술을 사용하는 것이 독점적이지 않은 이유이다. 우리는 그것을 책임지고 선택하는mixin과 쉽게 결합시킬 수 있기 때문에 다른 목록을 위해 다시 쓸 필요가 없다.

    소스 코드


    this Stackblitz link에서 모든 예시 코드를 찾을 수 있습니다.

    마지막 한마디


    본고에서 우리는 구성 요소 간에 코드를 공유하는 세 가지 기술을 소개했다.
    만약 아직 잘 모르겠다면, 나는 상속과 다중 상속을 좋아하지 않지만, 나는 그것을 언제 사용하는지 알고 인식하는 것이 좋은 생각이고, 언제 좋지 않은지 여전히 매우 중요하다고 생각한다.
    다음 글에서 저는 Typescript 혼합에 더 많은 관심을 기울일 것입니다. 제가 보기에 이것은 구성 요소를 구축하는 가장 알려지지 않고 과소평가된 방식입니다.나는 계승으로 인해 코드가 취약하고 유지하기 어렵고mixin이 어떻게 도움을 제공하는지, 장단점과 자바스크립트 커뮤니티의 기존 기술을 포함하는 장면을 연구할 것이다.
    만약 당신이 어떤 해명이 필요하거나, 혹은 어떤 불명확하거나 잘못이 있다고 생각한다면, 논평을 남겨 주십시오!
    나는 네가 이 문장을 좋아하길 바란다.있다면 저Medium나 저website를 주목하고 소프트웨어 개발, 전단, RxJS, Typescript 등에 대한 더 많은 글을 알아보세요!

    좋은 웹페이지 즐겨찾기