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

Narrowing an interface's generic union property as though it was a local variable in typescript

我有一个 object 数据需要根据 object 当前包含的联合成员传递到两个位置之一。这两个地方都需要 object 中的所有数据,因此用缩小的类型重新创建 object 似乎有点傻。显然它有效。

作为替代方案,我尝试使 object 的接口在联合上通用,这样接口就可以在所有三个地方表示 object,认为自动类型缩小可能适用于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,
}

现在排名靠前的class可以使用TestData<DataTypes>,排名靠前的children可以使用TestData<UserData>TestData<ServerData>。在您尝试将 object 向下传递给 children 之一之前,这一切正常。编译器将正确缩小 TestDatadata 属性,但这不会缩小实际 object 的类型,它仍然具有 [=16= 的类型].这是一个例子。

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 
    }
}

此时我可以使用类型断言或(再次)创建一个新的 object 来传递正确的类型,但经过一些挖掘我发现 这给了我

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 块中所做的事情,但是如果 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(...) 所以 接近我正在寻找的东西,有没有办法将这两种行为结合起来以某种方式在 else 块中自动缩小整个通用 TestData<D> 类型?

这里的问题是 TestData 本身不是 discriminated union type,只有 data 属性 中的 D 是。换句话说,TS 可以通过 kind 判别式缩小 data,而不是外部 TestData 类型。

predicate 只能检查 TestData 是否包含特定类型 UserDataServerData,但它无法推断其他可能的联合部分与控制流中的if/else 块。可能的解决方案:

1) 缩小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 本身成为受歧视的联盟 (code)

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
    }
}