在 TypeScript 和/或 JSDoc 中,如何指示记录类型中的某些属性名称是同一类型中兄弟属性的别名?

Posted

技术标签:

【中文标题】在 TypeScript 和/或 JSDoc 中,如何指示记录类型中的某些属性名称是同一类型中兄弟属性的别名?【英文标题】:In TypeScript and/or JSDoc, how to indicate that some property names in a record type are aliases for sibling properties in the same type? 【发布时间】:2020-12-29 10:07:54 【问题描述】:

在 TS 中建模对象属性包类型(又名“记录”)的正确方法是什么,其中一些属性名称是其他属性的替代名称(又名“别名”)?特别是我们想让 VSCode 知道别名,以便它只会建议(在 IntelliSense 自动完成列表中)“主要”属性名称而不是别名,但我们也不希望 TS 编译器在用户手动键入时失败在别名中。

这里有更多背景信息:我们正在为一个 JS 库开发 .d.ts,其中包括接受类似时间的文字的函数,例如 hour: 12, minute: 30, second: 0。该库还接受这些文字属性的复数变体,例如hours: 12, minutes: 30, seconds: 0。使用复数变体不是最佳实践(除了允许使用它们的快速说明外,可能不会记录在案)。但是使用这些复数字符串也不会崩溃。

崩溃的情况是使用两个变体指定相同的单位:例如hour: 12, hours: 12.

我们应该如何在 TS 中对这种类型进行建模?

我们的目标:

从 IDE 自动完成功能中隐藏“错误”的变体。我假设我假设 JSDoc ignore 标记可用于此目的,但它没有列在 TS 文档的 supported tags list 中,而且它似乎在 VSCode 中没有做任何事情。 允许“错误”变体不出现 TS 编译器错误。 如果同一属性的单数和复数变体都包含在同一对象字面量中,则会产生 TS 编译器错误。 理想情况下,如果存在较小的单元而没有相应的较大单元,也会产生编译器错误。例如,hour: 10, second: 30 应该会产生错误。也就是说,由于联合类型的工作方式,我不确定这在 TS 中是否可行。 理想情况下,即使属性的别名不存在,单数和复数变体的混合也会产生编译器错误,例如hours: 10, minute: 5。如果 TS 未标记此错误也可以,因为与上面提到的“同一单元的 2 个变体”情况相比,这是一个罕见的错误。 理想情况下(这是可选的,因为我怀疑它可能不可能),复数变体将显示信息级 IDE 警告(VSCode 中的浅蓝色波浪线)以鼓励用户选择正确的变体而不是错误的,但不会破坏编译。

在 TypeScript 和/或 JSDoc 中对这些文字进行建模以实现所需行为或至少是其中的一个子集的推荐方法是什么?

这是包含两种变体的普通类型。该声明没有表现出上述任何期望的行为。

type Time = 
  hour?: number;
  minute?: number;
  second?: number;
  hours?: number;
  minutes?: number;
  seconds?: number;
;

JSDoc 的@alias 标签似乎最接近我们的意图,但它似乎在 VSCode 中没有做任何事情,并且它并没有免除复数变体的自动完成。我也不确定@alias 是否旨在处理别名指向兄弟成员的情况。上面链接的 JSDoc 手册中的所有示例都是指使用一个名称定义成员但在运行时使用另一个名称的情况,例如与类名前缀一起使用的静态类函数。我没有看到任何使用 @alias 镜像另一个现有同级属性的示例。

我们可以使用 JSDoc @deprecated 但这意味着这些值过去是可以的,但现在不是了,这并不完全正确。但它确实提供了一条消息来提醒用户使用其他变体。这似乎很有用。

JSDoc 的 @ignore 标记似乎也可能有所帮助,但它似乎在 VSCode 中也没有做任何事情,而且它也不能免除复数变体的自动完成。

【问题讨论】:

【参考方案1】:
从 IDE 自动完成功能中隐藏“错误”变体。 允许“错误”变体不出现 TS 编译器错误。

我能想到的最接近的是new support for /** @deprecated */ in typescript 4.0.。这不会将它们从自动完成中隐藏起来,但它会用一条线标记它们以阻止它们的使用。

我确实相信可以从自动完成中隐藏任何根据类型系统也是有效属性的内容。最好的办法是标记为不鼓励使用。

是的,这不是 @deprecated 的完美使用,但如果你想要 IDE 级别提示,那是你能得到的最好的。

type Time = 
  hour?: number;
  minute?: number;
  second?: number;
  
  /** @deprecated */
  hours?: number;

  /** @deprecated */
  minutes?: number;

  /** @deprecated */
  seconds?: number;
;


对于其余部分,这听起来不像您想要一个单一的界面。相反,您想要一个可能的组合的联合。我认为解决这个问题的唯一合理方法是为每个允许的组合创建一个类型。

但您还想禁止某些组合中的某些属性,您可以使用 foo?: undefined 之类的东西来做到这一点。

所以你可以把一些东西放在一起:

// Singular Components
type Hour =  hour: number 
type Minute =  minute: number 
type Second =  second: number 

type NoHour =  hour?: undefined 
type NoMinute =  minute?: undefined 
type NoSecond =  second?: undefined 
type NoSingular = NoHour & NoMinute & NoSecond

// Plural Components
type Hours =  hours: number 
type Minutes =  minutes: number 
type Seconds =  seconds: number 

type NoHours =  hours?: undefined 
type NoMinutes =  minutes?: undefined 
type NoSeconds =  seconds?: undefined 
type NoPlural = NoHours & NoMinutes & NoSeconds

// Singular time type
type SingularTime = NoPlural & (
  | (Hour & NoMinute & NoSecond)
  | (Hour & Minute & NoSecond)
  | (Hour & Minute & Second)
)

// Plural time type
type PluralTime = NoSingular & (
  | (Hours & NoMinutes & NoSeconds)
  | (Hours & Minutes & NoSeconds)
  | (Hours & Minutes & Seconds)
)

// Final time type
type Time = SingularTime | PluralTime

(为简洁起见,我省略了/** @deprecated */,但你必须在复数属性出现时添加它)

可以这样使用:

// Good
const singular: Time =  hour: 1, minute: 2, second: 3 
const plural: Time =  hours: 1, minutes: 2, seconds: 3 

// Errors:
const samePropMix: Time =  hour: 1, hours: 2, minute: 3, second: 4 
const diffPropMix: Time =  hour: 1, minutes: 2, second: 3 
const missingMinutes: Time =  hour: 1, second: 3 

Playground

【讨论】:

以上是关于在 TypeScript 和/或 JSDoc 中,如何指示记录类型中的某些属性名称是同一类型中兄弟属性的别名?的主要内容,如果未能解决你的问题,请参考以下文章

用于接口属性的 TypeScript jsdoc

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

[TypeScript] Type check JavaScript files using JSDoc and Typescript 2.5

TypeScript JSDoc 风格的类型是不是具有常规类型的全部功能?

第1639期如何使用 JSDoc 保证你的 Javascript 类型安全性

VS Code 中的 Vue Array Prop JSDoc TypeScript 错误:“ArrayConstructor”类型缺少“MyCustomType []”类型中的以下属性