TypeScript 装饰器的副作用

v1l68za4  于 6个月前  发布在  TypeScript
关注(0)|答案(9)|浏览(69)

🔎 搜索词

"decorator" "field decorator" "side effect" "decorator side effect"

🕗 版本与回归信息

  • 在版本5.0和5.2之间发生了变化
  • 在我尝试的每个版本中,我都检查了关于装饰器的FAQ条目

⏯ Playground链接

https://www.typescriptlang.org/play?#code/MYewdgzgLgBAYgSwKYBsAmARJoBOBDKEHGAXhgAoo8cBzJKALhgFcw0kAzBMJNAGhigwUJAA9GMAMIo8ECIlSZsRAkUngR4gJSkAfDADeAXwBQJ4DLkwACtSTDDJmIPDQczYIRzlqNAIxMbtw0Ar4ATIFQOMGhtADMkdFgNDoGTs4wAPSZMAACUBAAtAg0YERI6c5QABYIEAB0AA44II1+pDC+fpVZOflFJWU4FRkwNXVNLY1hHeE92XkFxaXlPeMNza1xs-Hpps6bbQCEicFmB1NhJzBByecwh3HXtzQmpuaWEFK16DBiImwvrZhg40hdWn5nlEzulDldTnd0rkFOgsLhVDhYVMngjXu8hNBOh0eAB3b4IdDkADkfj8VIEVLCYXpMCpcTiVK05lcIBQSHqKBANB8XIWeEmENIJDIrHYXB4aF6MAA6gAVOAmMUS6ZSsiM5lKgDyAGlNTlxY9dSw2JxuLwlWqNUA

💻 代码

const FieldDecorator = (target: undefined, context: ClassFieldDecoratorContext) => {} // do nothing

class Parent {
  constructor(arg1: string, arg2: string, arg3: string) {
    // @ts-ignore
    this.prop1 = arg1
    // @ts-ignore
    this.prop2 = arg2
    // @ts-ignore
    this.prop3 = arg3
  }
}

class Child extends Parent {
  prop1!: string

  prop2!: string

  @FieldDecorator
  prop3!: string
}

const a = new Child('11', '22', '33')
console.log(a)
// a.prop1 === undefined // WTF
// a.prop2 === '22' // OK
// a.prop3 === undefined // WTF

🙁 实际行为

尽管在构造函数中给定了一个值,但被装饰的属性和第一个属性仍然是 undefined

🙂 预期行为

a 对象应该等于以下 obj 对象

const obj = {
    prop1: "11"
    prop2: "22"
    prop3: "33"
} 
4ngedf3f

4ngedf3f1#

这三个属性都应该是undefined,因为根据ES规范,在派生类中声明的类字段是在基类构造函数运行之后初始化的。prop2看起来像是一个bug。

fivyi3re

fivyi3re2#

@fatcerberus 你是说,即使没有装饰器,所有的字段都应该是 undefined 吗?这听起来很荒谬。这也是一个bug吗?

class Parent {
  constructor(arg1: string, arg2: string, arg3: string) {
    // @ts-ignore
    this.prop1 = arg1
    // @ts-ignore
    this.prop2 = arg2
    // @ts-ignore
    this.prop3 = arg3
  }
}

class Child extends Parent {
  prop1!: string

  prop2!: string

  prop3!: string
}

const a = new Child('11', '22', '33')
console.log(a)
// a.prop1 === '11' // bug?
// a.prop2 === '22' // bug?
// a.prop3 === '33' // bug?

https://www.typescriptlang.org/play?#code/MYGwhgzhAEAKYCcCmA7ALtA3gKGtYA9ihGggK7BoEIAUiA5gIwBc0JCAlivQDTQMAmVuy69+CegGZhpUQEosuPNAD0K6AAE0EALQd6KakiV40ACw4QAdAAcEBG42gBecUxOr1W3fsPIP5pa29jYCLm4CHmqa2noGRgEW1nYOkuEMkkoAvtg52KCQMADCFiAAJtBIAB5oqGUw8MjoingpjgCEMpzc2EptAp1ssj19IZKDIj15hMQYYOEoSADu0CUc5TQA5IyMm3ybAgJ70JuSkpty+UQQBCBIViAE9HSX0WDBDk7O3yc7m57QABGZHoAH5sG8PqEXD8DkcAcCwRD1O82mlvq5TucESDwUA

o3imoua4

o3imoua43#

您正在使用现代装饰器与遗留类字段 "set" 行为相结合。
使用 useDefineForClassFields: true (或者将 target 更改为 es2020,稍后在 playground 中无法使用,但在本地使用 tsc 可以正常工作)会带来由 wisecerberus 描述的现代行为
https://www.typescriptlang.org/play?useDefineForClassFields=true#code/MYGwhgzhAEAKYCcCmA7ALtA3gKGtYA9ihGggK7BoEIAUiA5gIwBc0JCAlivQDTQMAmVuy69+CegGZhpUQEosuPNAD0K6AAE0EALQd6KakiV40ACw4QAdAAcEBG42gBecUxOr1W3fsPIP5pa29jYCLm4CHmqa2noGRgEW1nYOkuEMkkoAvtg52KCQMADCFiAAJtBIAB5oqGUw8MjoingpjgCEMpzc2EptAp1ssj19IZKDIj15hMQYYOEoSADu0CUc5TQA5IyMm3ybAgJ70JuSkpty+UQQBCBIViAE9HSX0WDBDk7O3yc7m57QABGZHoAH5sG8PqEXD8DkcAcCwRD1O82mlvq5TucESDwUA
我认为最好使用现代定义语义与现代装饰器相结合,或使用旧的 set 语义与实验性的(遗留)装饰器相结合,但不要将它们混合在一起。

llmtgqce

llmtgqce4#

@IllusionMH 感谢澄清。这比我预期的要复杂得多。想要用新的装饰器来取乐,结果却变成了折磨。顺便说一下,如果“混合它们”是个坏主意,那么为什么它是默认配置?

tjvv9vkg

tjvv9vkg5#

顺便说一下,如果"混合它们"是个坏主意,那么为什么它是默认配置?
之前的"默认"行为是你根本不能使用装饰器。一旦装饰器被标准化,你就可以在不选择的情况下使用它们。类字段行为也与先前的旧行为有关。

unguejic

unguejic6#

你能试着用数字代替字符串吗?另外,试着用数组作为输入而不是三个单独的变量或行,看看这些输出是否会改变,以便进一步探索。

svdrlsy4

svdrlsy47#

这比我预期的要复杂得多。想要用新的装饰器玩乐的心情变成了折磨。

提示:确保始终使用 useDefineForClassFields: true ,这样一切都像普通的JavaScript一样工作(我喜欢为 targetmodule 使用 esnext ,并使用测试来捕获我编写了过于新的语法的情况,然后调整我的手写代码并避免转译,仅在 TS 中使用类型注解)。

在Chrome控制台中运行此代码显示定义语义:

class Base {
  constructor() {
      this.foo = 123
  }
}

class Sub extends Base {
    foo
}

console.log(new Sub().foo) // undefined (expected)

顺便说一下,如果“混合它们”是个坏主意,那么为什么它是默认配置?
这是一个非常好的观点!
TypeScript 5允许使用标准装饰器,同时将 useDefineForClassFieldsfalse 保留为默认值(由于默认目标是超级旧的ES3目标,可能没有人需要默认使用这些功能),这导致人们编写了容易在纯JavaScript中出现问题的新装饰器代码(例如,如果有人复制/粘贴他们的装饰器到另一个纯JS项目中,或者有人从代码中去除类型以便在另一个纯JS项目中使用它,但它不起作用)。

r7s23pms

r7s23pms8#

看起来这个bug出现在我们在useDefineForClassFields: false中如何发出类字段,而装饰器存在的情况下。在#56955中,我们更新了我们的emit以匹配pzuraq/ecma262#12中对装饰器的规范性更改。为了确保装饰器和初始化器的正确时机,我们将字段初始化器与由字段装饰器添加的"额外"初始化器交错,通过将它们注入到以下字段的初始化器中。
这里的bug是,即使useDefineForClassFieldsfalse,我们仍然会进行这种注入。结果是,这会向派生构造函数引入之前被省略的字段赋值。

f0brbegy

f0brbegy9#

这里的解决方法是使用"declare"声明类成员。
修复此问题的预计时间是什么时候?

相关问题