我发现了一种情况,如果typescript中的枚举有一个或多个值,并且我不知道原因,那么它就会产生影响。
看看这个简化的示例代码:
// 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
})
错误消失了,一切又恢复了正常。
所以我的假设是当枚举只有一个值时,typescript推断类型
T
为ActionTypes.A
。但是如果枚举有一个以上的值,那么typescript就不能再推断这个了?但为什么会这样?在给定的示例中,在对象文字中明确定义了
T
ActionTypes.A
的信息。{
type: ActionTypes.A
}
但更普遍的问题是:
为什么枚举值的数量对编译器很重要?
这不是很危险,因为它会以意想不到的方式破坏行为吗?
最佳答案
当TypeScript遇到一个字面值“cc> /ccc> /cc>类型时,会有很多事情发生。有时编译器会将类型加宽到string
/number
/相关-enum
。其他时候编译器将保留该类型作为文字值。描述哪一个会发生在哪里并不简单。如果你敢的话,你可以read all about it。
在您的情况下,在string
中有一个泛型类型参数number
,您希望被推断为窄文字类型enum
,但实际上被推断为T
,构成了enhance()
的每个文字类型的联合。如果ActionTypes.A
是唯一的元素,那么类型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>(...)
的特殊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 🙁
特殊约束甚至可以是类型的并集,只要并集的某个元素包含
T
或string
且未被其他元素吸收: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
这里,
number
本质上与string
或number
完全相同,因为任何东西都可以分配给它。但由于它是包含Narrowable
和unknown
元素的事物的联合,因此它可以作为在泛型约束中缩小范围的提示。(请注意,any
不起作用,因为它只会变成string
。这很棘手。最后,我们可以改变
number
来给出缩小的提示: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
它起作用了!希望能有所帮助。祝你好运。