关于何时在TypeScript

时间:2018-03-24 06:27:03

标签: javascript angular typescript decorator

我一直认为TypeScript中的装饰器是在类的构造函数之后调用的。但是,有人告诉我,例如,this帖子声称在声明类时调用装饰器 - 而不是在实例化对象时调用。我注册的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>

当我执行代码时,控制台会记录&#34;默认字符串&#34;而不是&#34;改变字符串&#34;。这证明了在类的构造函数执行后调用装饰器。

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

3 个答案:

答案 0 :(得分:7)

  

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

这是对的。

作为@ 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.主要目的是提供元数据

  

当我执行代码时,控制台会记录“default string”而不是   “改变了字符串”。这证明了修饰后的调用   类的构造函数执行。

在您知道@Input()装饰者做什么之前,您无法确定。

Angular @Input装饰器只是装饰组件属性,包含一些信息。

这只是元数据,TestComponent.__prop__metadata__属性中的stored

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

enter image description here

第2步。角度编译器。

现在是角度编译器收集有关组件的所有信息(包括@Input元数据以生成组件工厂)的时间。准备好的元数据如下:

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

注意:当Angular TemplateParser遍历模板时,它使用此元数据to check指令是否具有名称为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.更改检测

在角度生成所有工厂并初始化所有必需的对象之后,它会从顶视图到所有子视图循环更改检测。

在此过程中,角度调用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财产gets value的第一个地方:

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属性值。变更检测机制负责此类事情。

答案 1 :(得分:2)

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

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

这是因为在ngOnInitngOnChanges更新您的输入后调用了ngOnChanges

答案 2 :(得分:1)

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

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();

会产生这个:

&#13;
&#13;
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();
&#13;
&#13;
&#13;

如您所见,类声明时在属性上调用__decorate函数。这根据装饰器代码重新定义属性。对于Angular,这可能只是设置一些元数据,为类输入启动。在这里它直接设置值。

因此,这里的属性已经在构造函数中发生了变化。