我发现了一种情况,如果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推断类型TActionTypes.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是唯一的元素,那么类型ActionTypesenum是相同的,您不会看到问题。但是当ActionTypes.A相当于ActionTypes时,你会遇到问题,因为ActionTypes.A的返回类型变成了ActionTypes而不是ActionTypes.A | ActionTypes.B。不能将enhance()值分配给SpecialAction<ActionTypes>,而不首先检查它或使用类型断言。
再次,你希望SpecialAction<ActionTypes.A>被推断为SpecialAction<ActionTypes>。实际上,如果您手动指定类型参数SpecialAction<ActionTypes.A>,如T,它是有效的。如何让编译器推断ActionTypes.A的较窄类型?如果在泛型类型上放置一个包含Tenhance<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 🙁

特殊约束甚至可以是类型的并集,只要并集的某个元素包含Tstring且未被其他元素吸收:
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本质上与stringnumber完全相同,因为任何东西都可以分配给它。但由于它是包含Narrowableunknown元素的事物的联合,因此它可以作为在泛型约束中缩小范围的提示。(请注意,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

它起作用了!希望能有所帮助。祝你好运。

10-06 05:26