[英]Narrowing an interface's generic union property as though it was a local variable in typescript
我有一个数据对象,我需要根据对象当前包含的联合成员将数据传递到两个位置之一。 这两个地方都需要对象中的所有数据,因此重新创建具有缩小类型的对象似乎有点愚蠢。 显然它虽然有效。
作为替代方案,我尝试使对象的接口在联合上泛型,以便接口可以在所有三个位置表示对象,认为自动类型缩小可能适用于TestData
的类型参数。
interface UserData {
kind: 'user',
user: string,
}
interface ServerData {
kind: 'server',
url: string,
}
type DataTypes = UserData | ServerData
interface TestData<D extends DataTypes> {
data: D,
id: string,
}
现在顶级可以使用TestData<DataTypes>
,孩子们可以使用TestData<UserData>
或TestData<ServerData>
。 这工作正常,直到您尝试将对象传递给其中一个孩子。 编译器会正确地缩小TestData
的data
属性的范围,但这不会缩小实际对象的类型,实际对象的类型仍然是TestData<DataTypes>
。 这是一个例子。
function basicNarrow(test: TestData<DataTypes>) {
if (test.data.kind === 'user') {
// Correctly narrowed to `UserData`
test.data.user
// Error: Generic type not narrowed, still `TestData<DataTypes>
const typed: TestData<UserData> = test
} else {
// Correctly narrowed to `ServerData`
test.data.url
// Error: Generic type not narrowed, still `TestData<DataTypes>
const typed: TestData<ServerData> = test
}
}
在这一点上,我可以使用类型断言或(再次)创建一个新对象来传递正确的类型,但经过一番挖掘后,我发现了一个类似问题的答案,它给了我
type NarrowKind<T, N> = T extends { kind: N } ? T : never;
function predicateNarrow(test: TestData<DataTypes>) {
const predicate = <K extends DataTypes['kind']>(narrow: TestData<DataTypes>, kind: K): narrow is TestData<NarrowKind<DataTypes, K>> => (
narrow.data.kind === kind
)
if (predicate(test, 'user')) {
// Correctly narrowed to `UserData`
test.data.user
// Success! Generic type narrowed to `TestData<UserData>
const typed: TestData<UserData> = test
} else {
// Error: Not narrowed
test.data.url
// Error: Generic type not narrowed, still `TestData<DataTypes>
const typed: TestData<ServerData> = test
}
}
这完成了我在if
块中所做的工作,但编译器不会缩小到else
块中的备用情况,而无需另外显式检查data
是否只是局部变量。
这是我希望缩小类型最终成为理想情况的示例
function idealNarrow(test: TestData<DataTypes>) {
function isKind(/*???*/) { /*???*/ }
if (isKind(test, 'user')) {
const user: UserData = test.data
const typed: TestData<UserData> = test
} else {
const server: ServerData = test.data
const typed: TestData<ServerData> = test
}
}
任何一种解决方案都可以毫无问题地使用,但是predicateNarrow(...)
与我正在寻找的非常接近,有没有办法以某种方式组合这两种行为以自动缩小整个通用TestData<D>
类型否则阻止?
这里的问题是TestData
本身不是可区分的联合类型,只有包含data
属性的D
是。 换句话说,TS 可以通过kind
判别式缩小data
,而不是外部TestData
类型。
predicate
只能检查TestData
以包含特定类型UserData
或ServerData
,但它无法推断其他可能的联合部分与if/else
块中的控制流。 可能的解决方案:
1) Narrow DataTypes
和重组TestData
( code )
function basicNarrow({ id, data }: TestData<DataTypes>) {
if (data.kind === 'user') {
data // UserData
const typed: TestData<UserData> = { id, data }
} else {
data // ServerData
const typed: TestData<ServerData> = { id, data }
}
}
2) 使TestData
本身成为可区分的联合( 代码)
type DataTypes = UserData | ServerData
type TestData<D extends DataTypes> = D & { id: string }
function basicNarrow(test: TestData<DataTypes>) {
if (test.kind === 'user') {
test // UserData & { id: string; }
const typed: TestData<UserData> = test
} else {
test // ServerData & { id: string; }
const typed: TestData<ServerData> = test
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.