关于何时在TypeScript中调用装饰器的困惑

Edd*_*Lin 7 javascript decorator typescript angular

我一直认为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);
   }       
}
Run Code Online (Sandbox Code Playgroud)

app.component.html

<app-test testString="altered string"></app-test>
Run Code Online (Sandbox Code Playgroud)

当我执行代码时,控制台记录"默认字符串"而不是"更改字符串".这证明了在类的构造函数执行后调用decorator.

有人可以给我一个明确的答案,说明何时调用装饰器?因为我在网上的研究与我做的实验相矛盾.谢谢!

yur*_*zui 9

声明类时会调用装饰器 - 而不是在实例化对象时调用.

那是对的.

正如@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;
}());
Run Code Online (Sandbox Code Playgroud)

现在,让我们通过接下来的步骤来了解你的错误.

步骤1.主要目的是提供元数据

当我执行代码时,控制台记录"默认字符串"而不是"更改字符串".这证明了在类的构造函数执行后调用decorator.

直到你知道@Input()装饰器的作用,你才能确定.

Angular @Input装饰器只是用一些信息装饰组件属性.

它只是元数据,将存储TestComponent.__prop__metadata__属性中.

Object.defineProperty(constructor, PROP_METADATA, {value: {}})[PROP_METADATA]
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

第2步.角度编译器.

现在是角度编译器收集有关组件的所有信息(包括@Inputmetadatas)以生成组件工厂的时间.准备好的元数据看起来像:

{
  "selector": "app-test",
  "changeDetection": 1,
  "inputs": [
    "testString"
  ],
  ...
  "outputs": [],
  "host": {},
  "queries": {},
  "template": "{{testString}}"
}
Run Code Online (Sandbox Code Playgroud)

(注意:当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
      }));
}
Run Code Online (Sandbox Code Playgroud)

将包括在生产工厂:

在此输入图像描述

我们可以注意到,更新表达式是在父视图(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);  <====
Run Code Online (Sandbox Code Playgroud)

这是您的@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);
}
Run Code Online (Sandbox Code Playgroud)

如你所见,它发生在ngOnChanges钩子之前.

结论

Angular @Input在装饰器执行期间不更新属性值.变更检测机制负责此类事情.