繁体   English   中英

缩小接口的通用联合属性,就好像它是打字稿中的局部变量

[英]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> 这工作正常,直到您尝试将对象传递给其中一个孩子。 编译器会正确地缩小TestDatadata属性的范围,但这不会缩小实际对象的类型,实际对象的类型仍然是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以包含特定类型UserDataServerData ,但它无法推断其他可能的联合部分与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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM