Angular OnPush Component 주의해야 할 곳: 계속

5665 단어 Angular
전날 기사 에서 Angular OnPush Component에서 여러가지 주의해야 할 부분을 썼습니다.
  • 케이스 1: componentRef.changeDetectorRef.detectChanges() is confusing.
  • @component({
      selector: 'dynamic',
      template: `{{name}}`,
      changeDetection: ChangeDetectionStrategy.OnPush
    })
    export class Dynamic  {
      name = 'initial name';
    }
    
    @component({
      selector: 'my-app',
      template: `
        <b>Expected</b>: "initial name" changes to "changed name" after 2s<br>
        <b>Actual</b>:<br>
      `,
      changeDetection: ChangeDetectionStrategy.OnPush
    })
    export class AppComponent  {
      constructor(private _vcRef: ViewContainerRef,
         private _cfr: ComponentFactoryResolver, private _injector: Injector) {}
    
      ngOnInit() {
        const cmptRef = this._cfr.resolveComponentFactory(Dynamic).create(this._injector);
        this._vcRef.insert(cmptRef.hostView);
    
        setTimeout(() => {
            cmptRef.instance.name = 'changed name';
            cmptRef.changeDetectorRef.detectChanges(); // this will not update the DOM.
            // cmptRef.injector.get(ChangeDetectorRef).detectChanges(); // this will update the DOM.
        }, 2000);
      }
    }
    

    이 케이스가 Dynamic 의 OnPush Component 를 추가해, 이 component의 cmptRef.changeDetectorRef.detectChanges() 를 호출해도, 화면이 갱신되지 않는다고 하는 현상이 됩니다.
    그리고 cmptRef.injector.get(ChangeDetectorRef).detectChanges();에서 실행되면 화면이 업데이트되었습니다.

    그 원인이 cmptRef.changeDetectorRef 가 이 OnPush Component 의 changeDetectorRef 가 아니고, 그 Component 를 포함하는 Placeholder 의 HostView 의 changeDetectorRef 가 됩니다.



    그래서이 HostView의 detectChanges()를 호출하여 OnPush Component가 아직 Dirty가 아니기 때문에 화면이 업데이트되지 않습니다.
  • 케이스 2: ComponentFixture.detectChanges() is confusing
  • const myComp = TestBed.createComponent(OnPushComponent);
    myComp.componentInstance.abc = 123;
    myComp.detectChanges() // Does not work
    
    myComp.componentRef.injector.get(ChangeDetectorRef).detectChanges(); // This will work.
    

    케이스 1과 비슷하지만 테스트에도 이 현상이 있습니다.
  • 사례 3: ngDoCheck() 가 실행되었지만 실제로 ChangeDetection 가 실행되지 않았습니다.
  • @Component({
      selector: "onpush",
      template: `
        onpush: {{ name }} <br />
      `,
      changeDetection: ChangeDetectionStrategy.OnPush
    })
    export class OnPush implements DoCheck {
      name = "initial name";
    
      ngDoCheck() {
        console.log("docheck onpush");
      }
    }
    
    @Component({
      selector: "my-app",
      template: `
        <onpush></onpush>
        <button (click)="onClick()">Update onpush</button>
      `,
      changeDetection: ChangeDetectionStrategy.OnPush
    })
    export class AppComponent {
      @ViewChild(OnPush) onPushComp: OnPush;
      constructor() {}
    
      onClick() {
        this.onPushComp.name = 'new name';
      }
    }
    

    이 경우 OnPush가 업데이트되지 않는 것은 정확하지만 ngDoCheck가 실행 된 것이 이상합니다.
    그 이유는 HostView의 존재입니다. 실제로는 Component의 Lifecycle hooks가 부모의 View에 속하기 때문입니다.
  • 케이스 4: dev mode 에서 OnPush Component가 CheckNoChanges() 가 실행되고 있지 않습니다, 즉 어떤 패턴이라도 ExpressionChangedAfterItHasBeenCheckedError 이 될 수 없습니다.
  • import {
      Component,
      NgModule,
      ViewChild,
      ChangeDetectionStrategy,
      DoCheck,
      Input, AfterViewInit
    } from "@angular/core";
    import { BrowserModule } from "@angular/platform-browser";
    
    @Component({
      selector: "onpush",
      template: `
        onpush: {{ name }} <br />
        {{ input }}
      `,
      changeDetection: ChangeDetectionStrategy.OnPush
    })
    export class OnPush implements AfterViewInit {
      name = 'initial name';
    
      ngAfterViewInit() {
        this.name = 'updated name';
      }
    
    }
    
    @Component({
      selector: "my-app",
      template: `
        <b>Expected</b>: "initial name" changes to "new name" after button click<br />
        <b>Actual</b>:<br />
        <onpush></onpush>
      `,
      changeDetection: ChangeDetectionStrategy.Default
    })
    export class AppComponent {
      @ViewChild(OnPush) onPushComp: OnPush;
    
      constructor() {}
    }
    

    그 원인이 지금 Angular가 ExpressionChangedAfterItHasBeenCheckedError를 탐지하기 위해 개발 모드에서 두 번 Change Detection을 실행했습니다. OnPush의 경우, 1회째로 Dirty 플래그를 리셋트 해, 2회째가 Dirty가 아닌 상태가 되어, CheckNoChanges의 처리가 실행되지 않게 되었습니다.

    그렇다면 왜 HostView라는 개념이 있습니까? 조금 다양한 레거시의 원인이 있습니다.
  • Directive를 설계할 때, Directive가 HostElement가 없기 때문에, Directive의 Hooks나 하나를 Placeholder의 HostView에 두는 설계가 되었습니다.
  • Component도 Directive이므로(Component가 View가 있는 Directive), 설계를 통일하기 위해, Component에도 HostView를 가졌습니다.
  • 보통의 Component의 경우, 일부러 새로운 HostView를 만들 필요가 없고, Parent Component View를 HostView로서 이용한다
  • BootStrap/Dynamic Component의 경우, 작성했을 때, 부모를 모르기 때문에, 새롭게 한 개 HostView를 작성되었습니다.

  • 라는 것이 되었습니다.

    이 HostView가 개발자 의식하고 싶지 않은 것이므로, 개발자가 직접 접촉하는 케이스가 없을 것입니다만, 상기의 케이스로 조금 이해하기 어려운 것이 발생했습니다.

    이제 이러한 문제를 해결하기 위해 상당히 Change Detection의 데이터 구조 로직을 업데이트하는 것이 어려워 지금부터
  • Document를 강화하여 이러한 사례와 Work Around 방안을 명확히한다
  • 미래에 HostView를 제거하는 디자인을 고려하십시오.

    그렇게 됩니다.
    지금 내 쪽에서 1번째를 대응중입니다만, 여러분이 이러한 문제를 맞을 때, 이 기사가 참고가 되면 좋다고 생각합니다.

    이상입니다, 정말 고마워요.
  • 좋은 웹페이지 즐겨찾기