建议
🔍 搜索词
- 拓宽
- 泛型
- 开放
✅ 可实现性检查清单
我的建议符合以下准则:
- 这不会对现有的TypeScript/JavaScript代码造成破坏性的更改
- 这不会改变现有JavaScript代码的运行时行为
- 这可以在不根据表达式的类型发出不同的JS的情况下实现
- 这不是一个运行时特性(例如库功能、带有JavaScript输出的非ECMAScript语法、JS的新语法糖等)
- 这个特性将与TypeScript's Design Goals的其他部分一致。
⭐ 建议
目前,TypeScript在决定何时拓宽泛型类型时有一些规则,这可能会导致相对相似的函数签名产生令人困惑的结果:
declare function fn1<T>(items: Array<T>): T
declare function fn2<T>(items: Array<() => T>): T
declare function fn3<T>(items: Array<{ prop: T }>): T
let res1: number | string = fn1([42, "hi"])
let res2: number | string = fn2([() => 42, () => "hi"]) // string ("hi") is not a number
let res3: number | string = fn3([{ prop: 42 }, { prop: "hi" }]) // string ("hi") is not a number
目前,它完全取决于TypeScript在未提供类型参数时的决定,以及TypeScript多年来已经发生了几次变化。
如果TypeScript能给你更多关于这种行为的控制权,以指定你的泛型是否拓宽,那将会很有帮助。
请忽略语法,只是演示我希望它会出现在哪里
// widen
declare function fn<widen T>(items: Array<() => T>): T;
fn([() => 1, () => 2, () => "three"]) // => number | string
// do not widen
declare function fn<donotwiden T>(items: Array<T>): T;
fn([1, 2, "three"]) // ERR
📃 激励示例
使用TypeScript生成类型的越来越流行的方式是,已经有了许多专门用于从运行时值推断精确类型的TypeScript功能,甚至有像zod和io-ts这样的库可以执行类似的事情:
let type = union(string(), number())
let value: unknown = ...
assert(value, type)
value // >> string | number
用TypeScript编写的库越来越多地依赖于TypeScript的值到类型的推断作为其公共API的一部分。因此,当TypeScript改变关于何时拓宽与否的规则时,这可能会导致更剧烈的破坏性更改,有时需要重新设计这些库。
type Check<T> = (value: unknown) => value is T
function string(): Check<string> {...}
function number(): Check<number> {...}
function union<T>(...members: Assertion<T>[]): Check<T> {...}
let assert = union(string(), number()) // ERR: number is not a string
let assert = union<string | number>(string(), number())
现在,你可以通过一些技巧来欺骗TypeScript获得期望的拓宽行为(通过谎报实际类型)。但是,不能保证这种行为会在TypeScript版本之间保持不变。
💻 用例
type Check<T> = (value: unknown) => value is T
function string(): Check<string> {...}
function number(): Check<number> {...}
function union<widen T>(...members: Check<T>[]): Check<T> {...}
let assert1 = union(string(), number())
let assert2 = union<string | number>(string(), number())
type SomeUnionToStayInSyncWith = string | number
let assert3 = union<SomeUnionToStayInSyncWith>(string(), number())
7条答案
按热度按时间new9mtju1#
See Distributive Conditional Types
And this will make your examples work.
Example1
Example2
6psbrbz92#
当然,但是你已经改变了
union()
的类型参数签名。关键是类型参数是公共API的一部分mw3dktmi3#
关于
nowiden
的情况,我不确定如何推理这里描述的行为。这三个行中,很可能有一个错误或者没有错误,但我认为很难证明noWiden(arr1)
应该是一个错误,而且当我们内联这个表达式时,代码中没有什么有意义的变化。这里关于推断候选集合过程有很多微妙之处——尽管 OP 中的例子看起来相似,但从类型系统的Angular 来看,推断算法看到的是非常不同的东西。我不认为这令人惊讶,但据我所知,我们实际上在过去并没有在这方面打破太多(欢迎针对我自己学习的反例)。
我认为这里的直觉是“除非其中一个推断候选项是联合体,否则不要推断联合体”。...但在这些情况下,实际上其中一个推断候选项就是联合体。必须有一些更强大的理论原则来依赖于这种方式,以便在某种程度上在局部重构参数的情况下保持一致性。
在我看来,
widen
更加清晰。在候选集合之后有一个步骤,大致上说,如果我们不能通过制作联合体(在没有约束说明其他情况的情况下)产生一个统一的类型,那么就发出一个错误并回退到约束类型。这可以很容易地根据类型参数声明进行依赖,然后一切都会从那里开始工作。一些更实际的使用案例将有助于帮助考虑。
fhity93d4#
我并没有真正使用
nowiden
的场景,我只是在假设这个过程是如何在内部工作的。如果这是不合理的,我很乐意放弃它。在候选人收集之后有一个步骤,大致上说,如果我们不能通过制作联合(在没有约束说明其他情况的情况下)来产生一个统一的类型,那么就发出一个错误并回退到约束类型。这可以很容易地依赖于类型参数声明,从那里开始几乎所有事情都可以正常工作。
我想你可以为了讨论的目的将
widen
重命名为prefer-union
:它的操作方式如下:
实际上,TypeScript已经有了所需的
prefer-union
行为:然后,TypeScript在某些地方产生了联合,但可能不适合
prefer-union
:最后,TypeScript放弃了产生联合,而是选择了其他方式:
vwhgwdsa5#
以下是Markdown格式的翻译结果:
这是我们用于推断联合类型的用例。我没能解决在调用我们的
isOneOf
类型守卫时,需要手动声明联合类型的问题。一个新的关键字似乎是一个优雅的解决方法,所以给你我的支持。7qhs6swi6#
Question: Which place makes more sense for TypeScript internally?
ycggw6v27#
如果将
oneOf
的定义更改为,它不会对
产生错误。如果你确实想约束
a
和b
具有相同的类型(但你并不想),那么你需要其他的东西,所以当前declare function oneOf<T,U>(a: T, b: T): T
的行为似乎没有用例。至于这个
oneOf
的例子,也许问题应该是,和
是否打算表达相同的语义?(我认为是的)。
那么为什么它们表现不同呢?
前者短了4个字符。
或许可以称之为一个bug。
这里有另一个看似在语义上相同但遇到问题的泛型示例。