Typescript 生成带有 `#private;` 字段的声明 d.ts 文件

Posted

技术标签:

【中文标题】Typescript 生成带有 `#private;` 字段的声明 d.ts 文件【英文标题】:Typescript generates declaration d.ts file with `#private;` field 【发布时间】:2021-02-18 18:15:43 【问题描述】:

我有一个用 Typescript 编写的库,它分布在 2 个文件中:一个编译后的 ECMAScript-2015 兼容 javascript 文件 index.js 和一个 Typescript 声明文件 index.d.ts。我的目标是让 Javascript 和 Typescript 开发人员都可以访问库(以便他们有正确的类型和自动完成功能)。

最近我升级到 Typescript 3.9.7,并决定重构我的代码以使用新的私有类字段声明,该声明利用 # sigil 而不是 Typescript 的 private 关键字。

令我惊讶的是,我的 index.d.ts 文件与旧的 Typescript 版本不兼容,因为我的课程中包含了 #private; 成员。

这是生成旧声明文件的旧 Typescript 代码与生成新的不兼容声明文件的新重构 Typescript 代码之间的比较。旧代码使用private关键字:

// index.ts
class MyClass 
    private field1: string = "foo";
    private field2: string = "bar";

    constructor() 
        console.log(this.field1, this.field2);
    


// generated index.d.ts
declare class MyClass 
    private field1;
    private field2;
    constructor();

使用# sigil 声明私有名称的新重构代码:

// index.ts
class MyClass 
    #field1: string = "foo";
    #field2: string = "bar";

    constructor() 
        console.log(this.#field1, this.#field2);
    


// generated index.d.ts
declare class MyClass 
    #private;
    constructor();

这是包含该示例代码的a page at Typescript playground。

现在,如果我的客户使用旧的 Typescript(比如 3.7 版)将获取我的库(由编译后的 index.js 和声明文件 index.d.ts 组成,没有源文件 index.ts 文件)并依赖于 @ 987654336@ 类型,他们会看到以下错误:

error TS1127: Invalid character.

该错误的来源很清楚(# sigil),所以我的问题如下:

    如果我在将库发送给不需要了解实施细节的客户之前对index.d.ts 进行后处理并删除#private; 行,是否可以?我可以通过使用 ttsc 包轻松做到这一点,但我仍然担心输入信息可能很重要。 index.d.ts中的#private;行有什么实际用途?为什么声明文件会暴露一个类使用私有字段,如果它们无论如何都无法访问,并且是实现细节? 根据a topic in Typescript Github issues,这是预期的行为,以便具有私有字段的类在发送到.d.ts 文件时保留其名义类型的行为。可悲的是,这种解释的意义从我身上消失了。是否有任何额外的文档可供我阅读以更好地理解 Typescript 的名义打字行为?

【问题讨论】:

【参考方案1】:

#private 语法目前是用于 javascript 的 stage-3 proposal。一旦浏览器支持它,它就不会被打字稿转译。

【讨论】:

问题不在于私有字段是什么。这就是发射定义中神秘的#private 成员。 啊......所以听起来你的问题是“我如何发出向后兼容的打字稿定义文件”......我猜打字稿假设声明文件消费者会理解该语法。 【参考方案2】:

它使类型“名义上的”,以便公开相同公共成员的其他类型不被视为与具有私有字段的类型兼容。这很重要的一种情况是,如果您有这样的代码:

class C 
    #foo = "hello";
    bar = 123;

    static log(instance: C) 
        console.log("foo = ", instance.#foo, " bar = ", instance.bar);
    

我相信还有更多示例,但这个静态方法只是我想到的一个。

这个C.log 函数需要C 类的实际实例,因为它访问instance 参数上的私有命名实例字段。如果声明 emit 没有反映 C 类型是名义上的,表明它有一个 ES 私有字段,而是只发出公共字段,编译器将在此处使用结构类型比较并且不会产生预期的类型错误.例如,该声明发出将允许依赖代码将 bar: 456 传递给C.log,而不会出现任何编译器错误。

【讨论】:

确实,我尝试使用另一个 Test 类,它编译为与您的示例类相同的 .d.ts(相同的公共成员,不同的私有成员),当我输入 C.log(new Test); 时,我得到 “Test”类型中的属性“#private”指的是无法从“C”类型中访问的不同成员。。谢谢你的解释。 我请版主将我的问题与这个问题(原始问题)合并,以便您有资格获得赏金【参考方案3】:

我试图回答你的问题但无法回答,然后我出于好奇问了我自己的问题,TypeScript 贡献者回答了这个问题,你可以在这里找到他的答案:What's the purpose of #private in TypeScript definition files?

总而言之,在某些情况下,私有字段在与类型之间进行比较时很重要,这就是出现 #private 字段的原因,因此“包含私有成员”的信息是类型定义的一部分。

【讨论】:

以上是关于Typescript 生成带有 `#private;` 字段的声明 d.ts 文件的主要内容,如果未能解决你的问题,请参考以下文章

TypeScript 中的 private 关键字和 private 字段有啥区别?

PHP生成带有干扰线的验证码,干扰点字符倾斜

typescript TypeScript Private Constructos和Singletons

TypeScript 类

JSDoc Typescript声明:如何隐藏“私有”方法

带有 ESLint 的 TypeScript:解析错误:关键字“枚举”是保留的 eslint