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

问题描述 投票:1回答:1

我有一个数据对象,我需要根据该对象当前包含的联合成员将其传递到两个位置之一。两个地方都需要对象中的所有数据,因此使用缩小的类型重新创建对象似乎有点愚蠢。显然,它可以工作。

作为替代,我尝试使对象的接口在联合上通用,以便该接口可以在所有三个位置表示对象,并认为自动类型缩小可能适用于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 
    }
}

[此时,我可以使用类型断言,或者(再次)创建一个新对象以传递正确的类型,但是经过一番挖掘之后,我发现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块中吗?

我有一个数据对象,我需要根据该对象当前包含的联合成员将其传递到两个位置之一。这两个地方都需要对象中的所有数据,因此请使用...
typescript typescript-generics
1个回答
1
投票
这里的问题是TestData本身不是discriminated union type,仅包含D属性的data是。换句话说,TS可以将data的范围缩小为kind,而不是外部TestData类型。
© www.soinside.com 2019 - 2024. All rights reserved.