web-dev-qa-db-ja.com

TypeScriptでデコレータがいつ呼び出されるかについての混乱

TypeScriptのデコレータは、クラスのコンストラクタの後に呼び出されるという印象を受けました。ただし、別の方法で言われました。たとえば、 this postのトップアンサーは、オブジェクトがインスタンス化されたときではなく、クラスが宣言されたときにデコレータが呼び出されると主張しています。私が登録したAngularコースのUdemyインストラクターは、TypeScriptのデコレータがbeforeプロパティの初期化を実行することも教えてくれました。

しかし、この主題に関する私の実験はそうではないことを示しているようです。たとえば、これは単純なAngularプロパティバインディングのあるコードです:

test.component.ts

import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-test',
  template: '{{testString}}'  
})
export class TestComponent{    
  @Input() testString:string ="default string";    
  constructor() {
    console.log(this.testString);
   }       
}

app.component.html

<app-test testString="altered string"></app-test>

コードを実行すると、コンソールは「変更された文字列」ではなく「デフォルトの文字列」をログに記録します。これは、クラスのコンストラクターが実行された後にデコレーターが呼び出されることを証明しています。

デコレータがいつ呼び出されるかについて、誰かが明確な答えを教えてもらえますか?オンラインでの私の調査は、私が行った実験と矛盾しているためです。ありがとうございました!

10
Eddie Lin

デコレータは、オブジェクトがインスタンス化されたときではなく、クラスが宣言されたときに呼び出されます。

そのとおりです。

@ H.Bとしてすでに述べたように、トランスパイルされたコードを見ることでそれを証明することができます。

_var TestComponent = /** @class */ (function () {
    function TestComponent() {
        this.testString = "default string";
        console.log(this.testString);
    }
    __decorate([
        core_1.Input(),
        __metadata("design:type", String)
    ], TestComponent.prototype, "testString", void 0);
    TestComponent = __decorate([
        core_1.Component({
            selector: 'app-test',
            template: '{{testString}}'
        }),
        __metadata("design:paramtypes", [])
    ], TestComponent);
    return TestComponent;
}());
_

それでは、次の手順を実行して、どこが間違っていたかを理解しましょう。

ステップ1.主な目的はメタデータを提供することです

コードを実行すると、コンソールは「変更された文字列」ではなく「デフォルトの文字列」をログに記録します。これは、クラスのコンストラクターが実行された後にデコレーターが呼び出されることを証明しています。

@Input()デコレータが何をするかを知るまでは確信が持てません

Angular _@Input_デコレータは、いくつかの情報を含むコンポーネントプロパティを装飾します

これは単なるメタデータであり、_TestComponent.__prop__metadata___プロパティに stored になります。

_Object.defineProperty(constructor, PROP_METADATA, {value: {}})[PROP_METADATA]
_

enter image description here

ステップ2。Angularコンパイラ。

angularコンパイラが_@Input_メタデータを含むコンポーネントに関するすべての情報を収集して、コンポーネントファクトリを生成するときです。準備されたメタデータは次のようになります。

_{
  "selector": "app-test",
  "changeDetection": 1,
  "inputs": [
    "testString"
  ],
  ...
  "outputs": [],
  "Host": {},
  "queries": {},
  "template": "{{testString}}"
}
_

注:Angular TemplateParser テンプレートをウォークスルーすると、このメタデータが使用されます チェックする ディレクティブにtestStringという名前の入力があるかどうか)

メタデータコンパイラに基づく constructs updateDirective式:

_if (dirAst.inputs.length || (flags & (NodeFlags.DoCheck | NodeFlags.OnInit)) > 0) {
  updateDirectiveExpressions =
      dirAst.inputs.map((input, bindingIndex) => this._preprocessUpdateExpression({
        nodeIndex,
        bindingIndex,
        sourceSpan: input.sourceSpan,
        context: COMP_VAR,
        value: input.value
      }));
}
_

それは生産工場に含まれます:

enter image description here

上記で、更新式が親ビュー(AppComponent)で生成されていることがわかります。

ステップ3.変更の検出

angularがすべてのファクトリを生成し、必要なすべてのオブジェクトを初期化した後、上面図からすべての子ビューまで変化検出サイクルを実行します。

このプロセス中にangular呼び出し checkAndUpdateView 関数、ここでも pdateDirectiveFn

_export function checkAndUpdateView(view: ViewData) {
  if (view.state & ViewState.BeforeFirstCheck) {
    view.state &= ~ViewState.BeforeFirstCheck;
    view.state |= ViewState.FirstCheck;
  } else {
    view.state &= ~ViewState.FirstCheck;
  }
  shiftInitState(view, ViewState.InitState_BeforeInit, ViewState.InitState_CallingOnInit);
  markProjectedViewsForCheck(view);
  Services.updateDirectives(view, CheckType.CheckAndUpdate);  <====
_

それはあなたの_@Input_プロパティ 値を取得 :の最初の場所です

_providerData.instance[propName] = value;
if (def.flags & NodeFlags.OnChanges) {
  changes = changes || {};
  const oldValue = WrappedValue.unwrap(view.oldValues[def.bindingIndex + bindingIdx]);
  const binding = def.bindings[bindingIdx];
  changes[binding.nonMinifiedName !] =
    new SimpleChange(oldValue, value, (view.state & ViewState.FirstCheck) !== 0);
}
_

ご覧のとおり、ngOnChangesフックの前に発生します。

結論

Angularは、デコレータの実行中に_@Input_プロパティ値を更新しません。変化検出メカニズムはそのようなことを担当します。

10
yurzui

混乱の理由は、デコレータの動作ではなく、Angularが入力にバインドされたプロパティを更新する方法です。自分自身を証明して、

ngOnInit() {
  console.log(this.testString) // see updated value
}

これは、最初のngOnInitngOnChangesが入力を更新した後にngOnChangesが呼び出されるために発生します。

生成されたコードを見るだけです。

const defaultValue = (value: any) =>
  (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
    target[propertyKey] = value;
  };

class Test
{
  @defaultValue("steven")
  myProperty: string;

  constructor()
  {
    console.log(this.myProperty);
  }
}

new Test();

これを生成します:

var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var defaultValue = function (value) {
    return function (target, propertyKey, descriptor) {
        target[propertyKey] = value;
    };
};
var Test = /** @class */ (function () {
    function Test() {
        console.log(this.myProperty);
    }
    __decorate([
        defaultValue("steven")
    ], Test.prototype, "myProperty", void 0);
    return Test;
}());
new Test();

ご覧のとおり、__decorate関数はクラス宣言時にプロパティで呼び出されます。これにより、デコレータコードに従ってプロパティが再定義されます。 Angularの場合、これはおそらくメタデータを設定し、入力用にクラスをプライミングします。ここでは、値を直接設定します。

したがって、ここでは、プロパティはコンストラクタですでに変更されています。

1
H.B.