web-dev-qa-db-ja.com

リファクタリングAngular多くの入力/出力から単一の構成オブジェクトへのコンポーネント

私のコンポーネントは、多くの場合、複数の_@Input_および_@Output_プロパティを持つことから始まります。プロパティを追加すると、入力として単一の構成オブジェクトに切り替える方がクリーンなようです。

たとえば、複数の入力と出力を持つコンポーネントは次のとおりです。

_export class UsingEventEmitter implements OnInit {
    @Input() prop1: number;
    @Output() prop1Change = new EventEmitter<number>();
    @Input() prop2: number;
    @Output() prop2Change = new EventEmitter<number>();

    ngOnInit() {
        // Simulate something that changes prop1
        setTimeout(() => this.prop1Change.emit(this.prop1 + 1));
    }
}
_

そしてその使用法:

_export class AppComponent {
    prop1 = 1;

    onProp1Changed = () => {
        // prop1 has already been reassigned by using the [(prop1)]='prop1' syntax
    }

    prop2 = 2;

    onProp2Changed = () => {
        // prop2 has already been reassigned by using the [(prop2)]='prop2' syntax
    }
}
_

テンプレート:

_<using-event-emitter 
    [(prop1)]='prop1'
    (prop1Change)='onProp1Changed()'
    [(prop2)]='prop2'
    (prop2Change)='onProp2Changed()'>
</using-event-emitter>
_

プロパティの数が増えるにつれて、単一の構成オブジェクトへの切り替えがよりクリーンになる可能性があるようです。たとえば、次のコンポーネントは単一の構成オブジェクトを取ります。

_export class UsingConfig implements OnInit {
    @Input() config;

    ngOnInit() {
        // Simulate something that changes prop1
        setTimeout(() => this.config.onProp1Changed(this.config.prop1 + 1));
    }
}
_

そしてその使用法:

_export class AppComponent {
    config = {
        prop1: 1,

        onProp1Changed(val: number) {
            this.prop1 = val;
        },

        prop2: 2,

        onProp2Changed(val: number) {
            this.prop2 = val;
        }
    };
}
_

テンプレート:

_<using-config [config]='config'></using-config>
_

これで、ネストされたコンポーネントの複数のレイヤーを介して構成オブジェクト参照を渡すことができます。 configを使用するコンポーネントは、config.onProp1Changed(...)のようなコールバックを呼び出します。これにより、configオブジェクトは新しい値の再割り当てを行います。したがって、まだ一方向のデータフローがあるようです。さらに、プロパティの追加と削除では、中間レイヤーを変更する必要はありません。

複数の入力と出力を使用する代わりに、コンポーネントへの入力として単一の構成オブジェクトを使用することの欠点はありますか?このような_@Output_とEventEmitterを回避すると、問題が発生する可能性があります。後で私に追いつく?

22
Frank Modica

Inputsに単一の構成オブジェクトを使用することは問題ないと思いますが、常にOutputsに固執する必要があります。 Inputは、コンポーネントが外部から必要とするものを定義し、それらの一部はオプションの場合があります。ただし、Outputsは完全にコンポーネントのビジネスであり、内部で定義する必要があります。これらの関数の受け渡しをユーザーに依存している場合は、undefined関数を確認するか、イベントが多すぎる場合にコンポーネントを使用するのが面倒な場合がある構成内で常に渡されるかのように関数を呼び出す必要があります。ユーザーがそれらを必要としない場合でも定義します。したがって、常にOutputsをコンポーネント内で定義し、必要なものをすべて発行してください。ユーザーがそれらのイベントの関数をバインドしない場合、それは問題ありません。

また、configsに対して単一のInputを使用することはベストプラクティスではないと思います。実際の入力が非表示になり、ユーザーはコードまたはドキュメントの内部を調べて、何を渡す必要があるかを確認する必要があります。ただし、Inputsが個別に定義されている場合、ユーザーは 言語サービス)などのツールを使用してインテリセンスを取得できます。

また、変化検出戦略も破られる可能性があると思います。

次の例を見てみましょう

@Component({
    selector: 'my-comp',
    template: `
       <div *ngIf="config.a">
           {{config.b + config.c}}
       </div>
    `
})
export class MyComponent {
    @Input() config;
}

使ってみよう

@Component({
    selector: 'your-comp',
    template: `
       <my-comp [config]="config"></my-comp>
    `
})
export class YourComponent {
    config = {
        a: 1, b: 2, c: 3
    };
}

そして別々の入力のために

@Component({
    selector: 'my-comp',
    template: `
       <div *ngIf="a">
           {{b + c}}
       </div>
    `
})
export class MyComponent {
    @Input() a;
    @Input() b;
    @Input() c;
}

そしてこれを使いましょう

@Component({
    selector: 'your-comp',
    template: `
       <my-comp 
          [a]="1"
          [b]="2"
          [c]="3">
       </my-comp>
    `
})
export class YourComponent {}

上で述べたように、渡される値を確認するには、YourComponentのコードを確認する必要があります。また、これらのconfigsを使用するには、どこにでもInputと入力する必要があります。一方、2番目の例では、どの値が渡されているかを明確に確認できます。 Language Serviceを使用している場合は、インテリセンスを取得することもできます。

もう1つは、2番目の例の方がスケーリングに適しているということです。さらにInputsを追加する必要がある場合は、常にconfigを編集する必要があり、コンポーネントが破損する可能性があります。ただし、2番目の例では、別のInputを追加するのは簡単であり、作業コードに触れる必要はありません。

最後になりましたが、実際には双方向バインディングを提供することはできません。 Inputdataと呼ばれ、OutputdataChangeと呼ばれる場合、コンポーネントのコンシューマーは双方向バインディングシュガー構文と単純型を使用できることをおそらくご存知でしょう。

<your-comp [(data)]="value">

これにより、を使用してイベントを発行すると、親コンポーネントのvalueが更新されます。

this.dataChange.emit(someValue)

これが単一のInputについての私の意見を明らかにすることを願っています

編集

いくつかのInputsが内部に定義されている単一のfunctionの有効なケースがあると思います。複雑なオプション/構成を必要とすることが多いチャートコンポーネントのようなものを開発している場合、実際には単一のInputを使用することをお勧めします。これは、入力が1回設定され、変更されることはなく、グラフのオプションを1か所にまとめた方がよいためです。また、ユーザーは、凡例、ツールチップ、x軸ラベル、y軸ラベルなどを描画するのに役立ついくつかの関数を渡すことができます。この場合は、次のような入力がある方がよいでしょう。

export interface ChartConfig {
    width: number;
    height: number;
    legend: {
       position: string,
       label: (x, y) => string
    };
    tooltip: (x, y) => string;
}

...

@Input() config: ChartConfig;
4

個人的には、4つ以上の入力と出力が必要な場合は、コンポーネントを再度作成するためのアプローチを確認します。複数のコンポーネントである必要があり、何か問題があります。とにかく、私がそれだけの入出力を必要としても、この理由のために、私は1つの構成でそれを作りません:

1-次のように、入力と出力の内部に何があるべきかを知るのは難しいです:( html入力要素とラベルを持つコンポーネントを検討してください)

このコンポーネントが3つしかなく、1〜2か月後にこのプロジェクトに戻る必要がある場合、または他の誰かがあなたと共同作業するか、コードを使用する場合を想像してみてください。あなたのコードを理解するのは本当に難しいです。

2-パフォーマンスの欠如。 angularは、配列やオブジェクトを監視するのではなく、単一の変数を監視する方がはるかに安価です。最初に示した例を検討する以外に、ラベルを追跡するように強制する必要があるのはなぜですか。常に変化する値と一緒に変化することはありません。

3-変数の追跡とデバッグが困難。 angular自体にはデバッグが難しい紛らわしいエラーがありますが、なぜそれを難し​​くする必要があります。間違った入力または出力を1つずつ追跡して修正する方が、1つの構成で行うよりも簡単です。データの束の変数。

個人的には、コンポーネントをできるだけ小さくして、それぞれをテストすることを好みます。次に、大きなコンポーネントだけではなく、小さなコンポーネントから大きなコンポーネントを作成します。

更新:このメソッドを1回の入力に使用し、変更データはありません(ラベルなど)

@Component({
selector: 'icon-component',
templateUrl: './icon.component.html',
styleUrls: ['./icon.component.scss'],
inputs: ['name', 'color']
});

export class IconComponent implements OnInit {
 name: any;
 color: any;

 ngOnInit() {
 }
}

HTML:

<icon-component name="fa fa-trash " color="white"></icon-component>

このメソッドを使用すると、angularは、コンポーネントの内部または外部の変更を追跡しません。ただし、@ inputメソッドを使用すると、変数が親コンポーネントで変更されると、コンポーネントの内部でも変更が行われます。

10
molikh
  • 明白な機能に加えてInputを持つことのポイントは、コンポーネントを宣言的で理解しやすいものにすることです。

  • 上記のすべての理由とテストのために、すべての構成を1つの巨大なオブジェクトに配置することは間違いなく成長します(私を信じてください)のは悪い考えです。

  • 巨大な紛らわしいオブジェクトを提供するよりも、単純なinputプロパティを使用してコンポーネントの動作をテストする方がはるかに簡単です。

  • さかのぼって、jQueryプラグインが機能していた方法のように考えています。ここでは、initという関数を呼び出してから、提供する必要があるかどうかさえ覚えていない一連の構成を提供します。かどうかにかかわらず、コンポーネント全体にこの未知の成長し続けるオブジェクトをコピーして貼り付け続けると、おそらくそれらは必要ありません。

  • デフォルトの作成は、単純なInputsを使用すると非常に簡単で明確ですが、作成されたデフォルトのオブジェクトでは少し面倒になります。

類似のInputOutputsが多すぎる場合は、以下を検討してください。

1- Baseクラスを作成し、類似しているすべてのInput/Outputを配置して、そこからすべてのコンポーネントを拡張できます。

export class Base{
    @Input() prop1: number;
    @Output() prop1Change = new EventEmitter<number>();
    @Input() prop2: number;
    @Output() prop2Change = new EventEmitter<number>();
}

@Component({})
export class MyComponent extends from Base{
      constructor(){super()}
}

2-これが気に入らない場合は、コンポジションを使用して再利用可能なmixinを作成し、そのようにすべてのInput/Outputを適用できます。

以下は、ミックスインを適用するために使用できる関数の例です。NOTEは必ずしも希望どおりであるとは限らないため、必要に応じて調整する必要があります。

export function applyMixins(derivedCtor: any, baseCtors: any[]) {
  baseCtors.forEach(baseCtor => {
    Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
      derivedCtor.prototype[name] = baseCtor.prototype[name];
    });
  });
}

そして、ミックスインを作成します。

export class MyMixin{
    @Input() prop1: number;
    @Output() prop1Change = new EventEmitter<number>();
    @Input() prop2: number;
    @Output() prop2Change = new EventEmitter<number>();
}

applyMixins(MyComponent, [MyMixin]);

3-入力のデフォルトプロパティを設定できるため、必要な場合にのみオーバーライドできます。

export class MyComponent{
    @Input() prop1: number = 10; // default 
}
4
Milad

複数の入力と出力を使用する代わりに、コンポーネントへの入力として単一の構成オブジェクトを使用することの欠点はありますか?

はい、 オンプッシュ変更検出戦略 に切り替えたい場合は、レンダリングサイクルが多すぎるために発生するパフォーマンスの問題を軽減するために、大規模なプロジェクトで必要になることがよくあります。angular構成オブジェクト内内部で発生した変更は検出されません。

このように@OutputとEventEmitterを回避すると、後で追いつく可能性のある問題が発生しますか?

はい、@Outputから離れ始め、テンプレートで構成オブジェクト自体を直接操作すると、ビューに副作用が発生します。これは、で見つけにくいバグの原因になります。未来。ビューは、挿入されるデータを決して変更しないでください。その意味で「純粋」なままで、イベント(または他のコールバック)を介して制御コンポーネントに何かが起こったことを通知するだけです。

更新:投稿の例をもう一度見てみると、入力モデルを直接操作するつもりはなかったようですが、 configオブジェクトを介してイベントエミッターを直接渡します。 @input(暗黙的に実行していること)を介してコールバックを渡すことにも、次のような 欠点 があります。

  • コンポーネントの理解と推論が難しくなります(入力と出力は何ですか?)
  • 使用できません バナナボックス構文 もう
3
B12Toaster

入力パラメータをオブジェクトとしてバンドルしたい場合は、次のようにすることをお勧めします。

export class UsingConfig implements OnInit {
    @Input() config: any;
    @Output() configChange = new EventEmitter<any>();


    ngOnInit() {
        // Simulate something that changes prop1
        setTimeout(() => 
          this.configChange.emit({
              ...this.config, 
              prop1: this.config.prop1 + 1
          });
        );
    }
}
  • プロパティを変更するときに、新しい構成オブジェクトを作成しています。
  • Output-Eventを使用して、変更された構成オブジェクトを発行しています。

どちらのポイントも、ChangeDetectionが正しく機能することを保証します(より効率的なOnPush戦略を使用していると仮定します)。さらに、デバッグの場合はロジックに従う方が簡単です。

編集:これが親コンポーネント内の明らかな部分です。

テンプレート:

<using-config [config]="config" (configChange)="onConfigChange($event)"></using-config>

コード:

export class AppComponent {
    config = {prop1: 1};

    onConfigChange(newConfig: any){
      // if for some reason you need to handle specific changes 
      // you could check for those here, e.g.:
      // if (this.config.prop1 !== newConfig.prop1){...

      this.config = newConfig;
    }
  }
2
Martin Cremer