带有动态 case 字符串的 switch-case 块的类型推断
Type-inference for switch-case block with dynamic case strings
棘手的 TS 挑战。
假设一个对象有两个对象 type
属性 和特定的 属性、fooData
或 foo2Data
,特定于 type
对象的值。
例如:
const dynamicString: string = 'abc'
const fooTypeName = `foo${dynamicString}` as const // const fooTypeName: `foo${string}`
const foo2TypeName = `foo${dynamicString}2` as const // const foo2TypeName: `foo${string}2`
type FooObj = {
type: typeof fooTypeName
fooData: string
}
type Foo2Obj = {
type: typeof foo2TypeName
foo2Data: string
}
假设将两种对象类型联合起来,创建一个“所有可能的对象”类型,AllObjs
:
type AllObjs = FooObj | Foo2Obj
现在,假设要创建一个函数来提取类型为 AllObjs
的对象的“数据”值。明智的做法是使用 switch-case 块,如下所示:
const extractData = (obj: AllObjs) => {
switch (obj.type) {
case fooTypeName:
return obj.fooData
case foo2TypeName:
return obj.foo2Data
default:
return null
}
}
当前有错误。由于 fooTypeName
和 foo2TypeName
的特定选择值,Typescript 无法在开关中进行类型推断。如果将 2
以外的任何字符附加到 fooTypeName
,或在其前面加上任何字符,它就会起作用。打字稿在这里做什么,为什么?
我认为这与集合论有关,以及 foo2TypeName
如何可能 包含 fooTypeName
。然而,仔细检查代码表明这是不可能的,因为对 dynamicString
的共享依赖(即不可能以 fooTypeName
和 [=20 的方式定义 dynamicString
=] 是一样的)。
Typescript就这么受限了吗?
编辑
正如@jcalz 和@catgirlkelly 所解释的,即使 fooTypeName
和 foo2TypeName
不可能相同(无论 dynamicString
的值是多少),因为每个引用dynamicString
被视为任何东西,foo${string}2
包含 foo${string}
,这是一个编译器限制,它不会将两个字符串视为不同的,因此防止 switch-case 块中的类型推断。
const dynamicString: string = 'abc'
// const fooTypeName = `foo${dynamicString}1` as const
const fooTypeName = `foo${dynamicString}` as const
const foo2TypeName = `foo${dynamicString}2` as const
fooTypeName
的类型是
`foo${string}`
foo2TypeName
的类型是
`foo${string}2`
fooTypeName
比 foo2Typename
宽,因此 TypeScript 将两者的并集简化为 foo${string}
,不允许您区分 AllObjs
的两个组成部分。
但为什么它更宽?
考虑以下情况:
fooTypeName = "fooAnythingGoes2" // ✅ foo, followed by any string
foo2TypeName = "fooAnythingGoes2" // ✅ foo, followed by any string and then 2
显然,这两个分配都是有效的,而且它们是相同的。现在,如果我们在 fooTypeName
的末尾添加任何其他字符:
const fooTypeName = `foo${dynamicString}.` as const // foo${string}.
它们不同:
fooTypeName = "fooAnythingGoes2" // foo, followed by any string and then .
fooTypeName = "fooAnythingGoes." // ✅ foo, followed by any string and then .
foo2TypeName = "fooAnythingGoes2" // ✅ foo, followed by any string and then 2
列出了更多可能有助于您理解的案例here。
棘手的 TS 挑战。
假设一个对象有两个对象 type
属性 和特定的 属性、fooData
或 foo2Data
,特定于 type
对象的值。
例如:
const dynamicString: string = 'abc'
const fooTypeName = `foo${dynamicString}` as const // const fooTypeName: `foo${string}`
const foo2TypeName = `foo${dynamicString}2` as const // const foo2TypeName: `foo${string}2`
type FooObj = {
type: typeof fooTypeName
fooData: string
}
type Foo2Obj = {
type: typeof foo2TypeName
foo2Data: string
}
假设将两种对象类型联合起来,创建一个“所有可能的对象”类型,AllObjs
:
type AllObjs = FooObj | Foo2Obj
现在,假设要创建一个函数来提取类型为 AllObjs
的对象的“数据”值。明智的做法是使用 switch-case 块,如下所示:
const extractData = (obj: AllObjs) => {
switch (obj.type) {
case fooTypeName:
return obj.fooData
case foo2TypeName:
return obj.foo2Data
default:
return null
}
}
当前有错误。由于 fooTypeName
和 foo2TypeName
的特定选择值,Typescript 无法在开关中进行类型推断。如果将 2
以外的任何字符附加到 fooTypeName
,或在其前面加上任何字符,它就会起作用。打字稿在这里做什么,为什么?
我认为这与集合论有关,以及 foo2TypeName
如何可能 包含 fooTypeName
。然而,仔细检查代码表明这是不可能的,因为对 dynamicString
的共享依赖(即不可能以 fooTypeName
和 [=20 的方式定义 dynamicString
=] 是一样的)。
Typescript就这么受限了吗?
编辑
正如@jcalz 和@catgirlkelly 所解释的,即使 fooTypeName
和 foo2TypeName
不可能相同(无论 dynamicString
的值是多少),因为每个引用dynamicString
被视为任何东西,foo${string}2
包含 foo${string}
,这是一个编译器限制,它不会将两个字符串视为不同的,因此防止 switch-case 块中的类型推断。
const dynamicString: string = 'abc'
// const fooTypeName = `foo${dynamicString}1` as const
const fooTypeName = `foo${dynamicString}` as const
const foo2TypeName = `foo${dynamicString}2` as const
fooTypeName
的类型是
`foo${string}`
foo2TypeName
的类型是
`foo${string}2`
fooTypeName
比 foo2Typename
宽,因此 TypeScript 将两者的并集简化为 foo${string}
,不允许您区分 AllObjs
的两个组成部分。
但为什么它更宽?
考虑以下情况:
fooTypeName = "fooAnythingGoes2" // ✅ foo, followed by any string
foo2TypeName = "fooAnythingGoes2" // ✅ foo, followed by any string and then 2
显然,这两个分配都是有效的,而且它们是相同的。现在,如果我们在 fooTypeName
的末尾添加任何其他字符:
const fooTypeName = `foo${dynamicString}.` as const // foo${string}.
它们不同:
fooTypeName = "fooAnythingGoes2" // foo, followed by any string and then .
fooTypeName = "fooAnythingGoes." // ✅ foo, followed by any string and then .
foo2TypeName = "fooAnythingGoes2" // ✅ foo, followed by any string and then 2
列出了更多可能有助于您理解的案例here。