TypeScript 当使用标准类字段时,参数属性的错误发射

f0brbegy  于 4个月前  发布在  TypeScript
关注(0)|答案(7)|浏览(62)

#50971上,@rbuckton指出了一个基于该bug的复现。当针对ES2022或更高版本的目标时,并且保持useDefineWithClassFields: true(默认值)不变:

class Helper {
    create(): boolean {
        return true
    }
}
export class Broken {
    constructor(readonly facade: Helper) {
        console.log(this.bug)
    }
    bug = this.facade.create()
}
new Broken(new Helper)

会产生

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Broken = void 0;
class Helper {
    create() {
        return true;
    }
}
class Broken {
    facade;
    constructor(facade) {
        this.facade = facade;
        console.log(this.bug);
    }
    bug = this.facade.create();
}
exports.Broken = Broken;
new Broken(new Helper);

目前,编译器会在bug = this.facade.create()上发出错误以避免错误的发射,但更改发射可能是更好的选择,当使用参数属性时:

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Broken = void 0;
class Helper {
    create() {
        return true;
    }
}
class Broken {
    facade;
    bug;
    constructor(facade) {
        this.facade = facade;
        this.bug = this.facade.create();
        console.log(this.bug);
    }
}
exports.Broken = Broken;
new Broken(new Helper);

不幸的是,这不是类字段的标准初始化顺序...所以它是正确的,但不是标准的。这可能没问题,因为参数属性本身就不是标准的。

xpszyzbs

xpszyzbs1#

这可能没问题,因为参数属性不是标准属性。
我认为这不是没问题的,因为你可能会因为类构造函数有一些参数属性而改变一组无关属性的初始化行为,这感觉很讨厌。
如果我启用了useDefineForClassFields,那么通常我这样做的原因是因为我想要根据标准行为的字段。如果这种行为与一些较旧的TS特性不兼容,我宁愿知道这一点,而不是让编译器尝试变得聪明,通过篡改事情使其工作。

jv4diomz

jv4diomz2#

我们在过去已经完成了类似的转换,例如我们为 accessor 字段所做的转换。我认为这个转换不会有任何主要问题,因为你可能会观察到的从这个emit发出的唯一副作用是一开始就依赖于它是一种不好的做法。

pn9klfpd

pn9klfpd3#

我感谢这个修复将使较新的模式与原始预期的行为保持一致。
对于"useDefineForClassFields"的用户来说,这是否会是一个破坏性的更改?

kt06eoxx

kt06eoxx4#

根据我所看到的,5.2版本之前的TypeScript状态是:Typescript不会报错,但在运行时可能会崩溃,具体取决于类字段的顺序与参数属性的顺序。5.2版本之后:Typescript报错。因此修复这个bug可以避免崩溃或编译错误;这听起来不像是一个破坏性的变化,除非在最严格的意义上,即“我预期程序会崩溃,但它成功终止了”。

jhkqcmku

jhkqcmku5#

当前编译器在遇到 bug = this.facade.create() 时会发出错误,以避免不良的发射,但将其改为在参数属性使用时更改发射可能会更好(...)
完全同意!这个 bug(直到修复)迫使我们继续使用 target: "es2021"
@fatcerberus 写道:
如果我启用 useDefineForClassFields,那么通常我这样做的原因是因为我想要根据标准行为的字段。
完全正确。据我理解,useDefineForClassFields 选项是为了引入 Define 语义而不是 Set 语义而引入的。提议的解决方案并没有改变这一点。
总的来说:关于这个 bugfix 是否可能是破坏性的更改,我的看法如下:

  • 要么人们一直使用目标 < es2022,到目前为止他们还没有使用过合适的 ES 类字段。他们将在切换到 es2022 输出时开始使用它们。因此,这对他们来说不可能是一个破坏性的更改(相反:这个 bugfix 防止了他们在使用参数属性初始化类字段时遭受严重的破坏性更改)
  • 或者人们正在使用普通的 JavaScript,可能严重依赖最新的 ES 标准(即规范中的类字段),并且迄今为止没有使用过 TypeScript。当切换到 TypeScript 时,这对他们来说也不是一个破坏性的更改,因为他们以前可能没有使用参数属性(因为它是 TypeScript 的功能)。
  • 或者人们一直在使用 TypeScript,并在 target: es2022 "一出现就切换"(即 TypeScript 4.6,于 2022年2月28日发布)。如果他们坚持使用这个目标,他们可能没有使用参数属性,或者至少没有使用参数属性来初始化类字段。否则,他们也会陷入这个 bug。因此,也许可以通过“不将内容拉入构造函数”来扩展提议的解决方案,当可用的参数属性未用于初始化类字段时。

最后的注意事项:
当前的行为“发出错误”并不总是有效,例如考虑这个 NgRx 效果,其中给 createEffect() 函数的“回调”是立即调用的,导致 cannot read property 'pipe' of 'undefined' (因为 this.actions$ 在此时仍然是 undefined,因为我们仍然处于示例化的中间阶段)
(示例取自 https://ngrx.io/guide/effects )

export class MoviesEffects {
 
  loadMovies$ = createEffect(() => this.actions$.pipe(
    ofType('[Movies Page] Load Movies'),
    exhaustMap(() => this.moviesService.getAll()
      .pipe(
        map(movies => ({ type: '[Movies API] Movies Loaded Success', payload: movies })),
        catchError(() => EMPTY)
      ))
    )
  );
 
  constructor(
    private actions$: Actions,
    private moviesService: MoviesService
  ) {}
}
kxxlusnw

kxxlusnw6#

关于这个有什么新消息吗?现在已经是2024年了,但由于这个问题,我们仍然被迫坚持使用target: "es2021"
附注:这个问题与#45995重复。

zfycwa2u

zfycwa2u7#

我认为这个问题应该尽快解决!
我刚刚发现,由于这个bug,Angular不得不在@angular-devkit/build-angular中强制修补他们的用户的tsconfig.json(参见esbuild工具和webpack工具)。实际上,这导致我们只剩下了target: es2022useDefineForClassFields=false,这与我原本的意图相反(我的tsconfig.json状态为target: es2021useDefineForClassFields=true)。@alan-agius4,你是否意识到你正在迫使所有Angular用户使用旧的类字段语义,除非他们明确设置useDefineForClassFields=true?(从源代码中看,这是你的初衷,但你通过仅检查本地创建的compilerOptions来实现它,而compilerOptions从未设置过useDefineForClassFields,因此它总是被强制设置为false。由于这种不正确的实现,现在使用webpack-tooling的所有用户甚至都没有选择权。
我可以很容易地想象这不会有一个好的结局......一旦TypeScript放弃对target: es2022(或更新版本)的支持,许多项目肯定会受到破坏。

相关问题