我发现一种情况,如果打字稿中的枚举具有1个或多个值,而我却不明白为什么。
看看这个简化的示例代码:
// library code
interface Action<T = any> {
type: T
}
interface SpecialAction<T = any> extends Action<T> {
id: string
timestamp: Date
}
function enhance<T>(action: Action<T>): SpecialAction<T> {
return {
... action,
id: "some-id",
timestamp: new Date()
}
}
// users code
enum ActionTypes {
A = "A"
}
interface UserAction extends SpecialAction<ActionTypes.A> {}
const value: UserAction = enhance({
type: ActionTypes.A
})
这很好用。但是,如果我这样更改枚举:
enum ActionTypes {
A = "A",
B = "B"
}
然后在const value: UserAction = enhance({
行中出现以下编译错误:
Type 'SpecialAction<ActionTypes>' is not assignable to type 'UserAction'.
Types of property 'type' are incompatible.
Type 'ActionTypes' is not assignable to type 'ActionTypes.A'.
如果我将代码更改为:
const value: UserAction = enhance<ActionTypes.A>({
type: ActionTypes.A
})
错误消失了,一切恢复正常。
所以我的假设是,当枚举只有一个值时,打字稿会推断类型T
为ActionTypes.A
。但是,如果枚举具有一个以上的值,那么Typescript无法再推断出这个值吗?
但是为什么会这样呢?在给定的示例中,T
是ActionTypes.A
的信息在对象文字
{
type: ActionTypes.A
}
但是更普遍的问题是: 为什么枚举值的数量对编译器很重要? 这不是很危险,因为它可能以意想不到的方式破坏行为?
答案 0 :(得分:3)
当TypeScript遇到文字string
/ number
/ enum
类型的值时,会发生很多事情。有时,编译器会将类型扩展为string
/ number
/ the-relevant-enum
。其他时候,编译器会将类型保留为文字值。描述哪一个将发生在何处并不是一件容易的事。如果您敢,可以read all about it。
在您的情况下,您想将T
的通用类型参数enhance()
推导为狭义文字类型ActionTypes.A
,但实际上将其推导为{{ 1}},即构成ActionTypes
的每种文字类型的并集。如果enum
是唯一元素,则类型ActionTypes.A
和ActionTypes
是相同的,那么您就不会遇到问题。但是,当ActionTypes.A
等效于ActionTypes
时,就会遇到问题,因为ActionTypes.A | ActionTypes.B
的返回类型变为enhance()
,而不是您期望的SpecialAction<ActionTypes>
。而且,您必须先检查SpecialAction<ActionTypes.A>
或使用类型断言才能将SpecialAction<ActionTypes>
的值分配给SpecialAction<ActionTypes.A>
。
同样,您希望将T
推断为ActionTypes.A
。确实,如果像T
中那样手动指定类型参数enhance<ActionTypes.A>(...)
,它就可以工作。我们如何让编译器推断T
的较窄类型?好吧,事实证明,如果您在通用类型上放置一个包含string
或number
的特殊constraint,编译器将提示您该类型为尽可能窄。例如:
declare function wide<T>(t: T): T;
let s = wide("abc"); // s is of type string
let n = wide(123); // n is of type number
declare function narrow<T extends string>(t: T): T;
let ss = narrow("abc"); // ss is of type "abc"
let nn = narrow(123); // error, 123 does not extend string
特殊约束甚至可以是类型的并集,只要该并集的元素之一包含string
或number
且不被其他东西吸收:
type Narrowable = string | number | boolean | symbol | object | void | undefined | null | {};
declare function betterNarrow<T extends Narrowable>(t: T): T;
let sss = betterNarrow("abc"); // sss is of type "abc"
let nnn = betterNarrow(123); // nnn is of type 123
let bbb = betterNarrow(true); // bbb is of type true
在这里,Narrowable
与unknown
或any
基本相同,因为几乎所有内容都可以分配给它。但是由于它是包含string
和number
作为元素的事物的结合,因此它暗示了缩小通用约束的范围。 (请注意,type Narrowable = string | number | unknown
不起作用,因为它将变成unknown
。这很棘手。)
所以最后我们可以更改enhance()
来缩小提示范围:
type Narrowable = string | number | boolean | symbol | object | void | undefined | null | {};
function enhance<T extends Narrowable>(action: Action<T>): SpecialAction<T> {
return {
...action,
id: "some-id",
timestamp: new Date()
}
}
const value: UserAction = enhance({
type: ActionTypes.A
}) // no error
有效!希望能有所帮助。祝你好运。