簡體   English   中英

關於何時在TypeScript中調用裝飾器的困惑

[英]Confusion about when decorators are called in TypeScript

我一直認為TypeScript中的裝飾器是在類的構造函數之后調用的。 但是,我被告知,例如, 這篇文章的最高答案聲稱在聲明類時調用Decorator,而不是在實例化對象時調用 我注冊的Angular課程的Udemy講師也告訴我,Typescript中的裝飾器在屬性初始化之前運行。

但是,我在這個問題上的實驗似乎表明不是這樣。 例如,這是一個帶有屬性綁定的簡單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>

當我執行代碼時,控制台記錄“默認字符串”而不是“更改字符串”。 這證明了在類的構造函數執行后調用decorator。

有人可以給我一個明確的答案,說明何時調用裝飾器? 因為我在網上的研究與我做的實驗相矛盾。 謝謝!

聲明類時會調用裝飾器 - 而不是在實例化對象時調用。

那是對的。

正如@HB已經說過的那樣,我們可以通過查看已編譯的代碼來證明這一點。

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.主要目的是提供元數據

當我執行代碼時,控制台記錄“默認字符串”而不是“更改字符串”。 這證明了在類的構造函數執行后調用decorator。

在知道@Input()裝飾器的作用之前,你無法確定

Angular @Input裝飾器只是用一些信息來裝飾組件屬性。

它只是元數據,將存儲TestComponent.__prop__metadata__屬性中。

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

在此輸入圖像描述

第2步。角度編譯器。

現在是角度編譯器收集有關組件的所有信息(包括@Input metadatas)以生成組件工廠的時間。 准備好的元數據看起來像:

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

注意:當Angular TemplateParser遍歷模板時,它使用此元數據來檢查指令是否具有名稱為testString輸入)

基於元數據編譯器構造 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
      }));
}

將包括在生產工廠:

在此輸入圖像描述

我們可以注意到,更新表達式是在父視圖(AppComponent)中生成的。

第3步。更改檢測

在角度生成所有工廠並初始化所有必需的對象后,它會從頂視圖到所有子視圖循環更改檢測。

在此過程中,angular調用checkAndUpdateView函數,它還調用updateDirectiveFn

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屬性值。 變更檢測機制負責此類事情。

混淆的原因不是由Decorator如何工作引起的,而是Angular如何更新它的輸入綁定屬性。 你可以證明自己和

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

這是因為ngOnInit被稱為第一后ngOnChangesngOnChanges更新您的輸入。

您可以查看生成的代碼,例如

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,這可能只是設置一些元數據,為類輸入啟動。 在這里它直接設置值。

因此,此處屬性已在構造函數中更改。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM