【问题标题】:How to do runtime type checking conditionally for conditional types如何有条件地对条件类型进行运行时类型检查
【发布时间】:2022-10-05 04:55:41
【问题描述】:

打字稿有conditional types。但是当我尝试在运行时使用泛型类型关联属性来分支一些处理逻辑时,我得到了以下类型错误。我想知道如何根据 multiple 属性的类型进行正确的类型推断?

type Value<T> = T extends undefined | false ? number : Array<number>

interface Props<T> {
    multiple: T
    onChange: (value: Value<T>) => void
}

function foo<T>(props: Props<T>) {
    if (props.multiple) {
        props.onChange([1, 2]) // Error: Argument of type \'number[]\' is not assignable to parameter of type \'Value<T>\'.
    } else {
        props.onChange(1) // Error: Argument of type \'number\' is not assignable to parameter of type \'Value<T>\'.
    }
}

顺便说一句,当我为 T 指定 false 时没有错误

function bar(props: Props<false>) {
    props.onChange(1)
}
  • 泛型条件类型很难使用。 TypeScript 不会通过控制流分析来缩小或重新限制泛型类型参数。检查if (props.multiple) {} 可以对props 产生影响,但确实如此没有什么T。这导致了各种 GitHub 问题建议,例如 ms/TS#33014ms/TS#33912,但到目前为止还没有任何处理它的地方。
  • 您可以继续使用泛型条件但通过断言this 来放弃函数内部的安全性,或者您可以放弃泛型条件并使用this 等可区分联合来获得安全性。这是否完全解决了您的问题?如果是这样,我可以写一个答案来解释;如果没有,我错过了什么? (如果您发表评论,请通过提及@jcalz 来通知我)
  • @jcalz歧视工会非常适合我的用例。欢迎回答以结束这个问题。还要感谢猫。
  • 好的,有机会我会写一个答案。

标签: typescript conditional-types


【解决方案1】:

Conditional types 依赖于 generic 类型参数很难使用。编译器确实对泛型类型参数本身执行任何control flow analysis,因此检查props.multipleT 没有影响。本质上,Value&lt;T&gt;foo() 实现内部的编译器是不透明的。有多个开放的 GitHub 问题在这里要求更好的东西,例如 microsoft/TypeScript#33014microsoft/TypeScript#33912。不过,现在,您需要放弃编译器验证的类型安全或通用条件类型。


如果您想保留通用条件类型,您可以使用type assertions 来抑制错误。你只是告诉props 具有特定类型的编译器。它可能看起来像这样:

function foo<T>(props: Props<T>) {
    if (props.multiple) {
        const _props = props as Props<true> // assert here
        _props.onChange([1, 2]) // okay
    } else {
        const _props = props as Props<undefined | false> // assert here
        _props.onChange(1) // okay
    }
}

这很好,但你要负责这里的类型安全。你可以很容易地交换东西(比如将if (props.multiple)更改为if (!props.multiple)),编译器不会注意到或抱怨:

function badFoo<T>(props: Props<T>) {
    if (!props.multiple) {
        const _props = props as Props<true>
        _props.onChange([1, 2]) // okay!
    } else {
        const _props = props as Props<undefined | false>
        _props.onChange(1) // okay!
    }
}

另一方面,如果您不关心通用条件类型,则可以改用discriminated union,它具有您正在寻找的确切行为。如果您有一个可区分联合类型的值,请检查判别式属性将缩小整个值。

它可能看起来像这样:

type Props = {
    multiple?: undefined | false;
    onChange: (value: number) => void
} | {
    multiple: true;
    onChange: (value: Array<number>) => void
}

function foo(props: Props) {
    if (props.multiple) {
        props.onChange([1, 2]) // okay
    } else {
        props.onChange(1) // okay
    }
}

这里Props 现在是一个有区别的联合,其中multiple 是区别属性。它是truefalse | undefined。当您检查prop.multiple 时,编译器会相应地缩小props

这意味着如果您执行错误的检查,编译器会注意到并抱怨:

function badFoo(props: Props) {
    if (!props.multiple) {
        props.onChange([1, 2]) // error!
    } else {
        props.onChange(1) // error!
    }
}

Playground link to code

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-10-31
    • 2023-02-09
    • 2018-05-19
    • 1970-01-01
    • 2016-11-04
    • 2016-08-30
    • 2013-11-02
    相关资源
    最近更新 更多