我有一个数据对象,我需要根据该对象当前包含的联合成员将其传递到两个位置之一。两个地方都需要对象中的所有数据,因此使用缩小的类型重新创建对象似乎有点愚蠢。显然,它可以工作。
作为替代,我尝试使对象的接口在联合上通用,以便该接口可以在所有三个位置表示对象,并认为自动类型缩小可能适用于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
}
}
[此时,我可以使用类型断言,或者(再次)创建一个新对象以传递正确的类型,但是经过一番挖掘之后,我发现this answer to a similar question给了我
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>
类型在else块中吗?