【问题标题】:TypeScript - narrowing type from generic union typeTypeScript - 从通用联合类型缩小类型
【发布时间】:2021-03-12 23:34:20
【问题描述】:
//Type declaration:

interface TickListFilter {
   type: "tickList";
   value: string;
}

interface ColorFilter {
   type: "color"
   value: ColorValueType
}

type Filter = TickListFilter | ColorFilter;



...
onValueChange = (filter: Filter, newValue: Filter["value"]) => {
        if (filter.type === "tickList") {
            // variable 'filter' has TickListFilter type (OK)
            // variable 'filter.value' has string type (OK)
            // variable newValue has ColorValueType | string. I know why, let's fix it!
        }
...

onValueChange = <T extends Filter>(filter: T, newValue: T["value"]) => {
        if (filter.type === "tickList") {
            // variable 'filter' has Filter type (wrong... i need tick list)
            // variable 'filter.value' has string | ColorValueType type (wrong, it should be string)
            // variable newValue has ColorValueType | string.
        }

我该如何解决这个问题?我知道它为什么会发生,因为 TS 不能通过泛型类型来区分联合。但是有什么解决方法可以像我描述的那样工作吗?

我想使用与 switch-case 类似的结构(不是 if-else only)

【问题讨论】:

    标签: typescript typescript-generics


    【解决方案1】:

    啊,欢迎来到microsoft/TypeScript#13995microsoft/TypeScript#24085。目前,编译器不使用控制流分析来缩小泛型类型参数或依赖于泛型类型参数的类型值。从技术上讲,缩小类型参数本身是错误的,因为有人可能会这样调用泛型版本:

    const filter: Filter = Math.random() < 0.5 ?
      { type: "tickList", value: "str" } : { type: "color", value: colorValue };
    const newValue: string | ColorValueType = Math.random() < 0.5 ? "str" : colorValue;
    onValueChange(filter, newValue); // no compiler error
    // const onValueChange: <Filter>(filter: Filter, newValue: string | ColorValueType) => void
    

    编译器将推断FilterT,根据泛型签名,这是“正确的”,但它会导致与您的非泛型版本相同的问题。在函数内部,newValue 可能与 filter 不匹配。 ?


    在上述 GitHub 问题中已经有很多关于如何处理这个问题的建议和讨论。如果类似microsoft/TypeScript#27808,您可以说类似T extends-exactly-one-member--of Filter 的意思,这意味着T 可以是TickListFilterColorFilter,但它可能不是Filter。但是现在还不能这么说。


    目前最简单的方法(假设您不想实际检查函数的实现内部filternewValue 是否相互匹配)将涉及type assertions 或类似的东西。您需要充分放松类型检查以允许代码,并且只是期望/希望没有人会像我上面那样调用您的函数。

    这是使用类型断言的一种方法:

    const onValueChange = <T extends Filter>(filter: T, newValue: T["value"]) => {
      const f: Filter = filter; // widen to concrete type
      if (filter.type === "tickList") {
        const v = newValue as TickListFilter["value"]; // technically unsafe narrowing
        f.value = v; // okay
      } else {
        const v = newValue as ColorFilter["value"]; // technically unsafe narrowing
        f.value = v; // okay
      }
    }
    

    Playground link to code

    【讨论】:

      【解决方案2】:

      如果您想要手动解决方案,可以自行编写type guards

      // Either like this, simple but a lot of typing
      const isColorFilter = (value: Filter): value is ColorFilter => value.type === 'color';
      
      // Or write a small type guard generator if there are lots of these types
      const createFilterTypeGuard = <T extends Filter>(type: T['type']) => (value: Filter): value is T => value.type === type;
      
      // And then
      const isColorFilter = createFilterTypeGuard<ColorFilter>('color');
      

      然后在你的代码中你可以像这样使用它们:

        if (isColorFilter(filter)) {
          // filter is ColorFilter
        }
      

      但是,您的newValue 会遇到问题。由于类型保护只会缩小filter 的类型,newValue 将保持不缩小:

      onValueChange = <T extends Filter>(filter: T, newValue: T["value"]) => {
        // OK, filter is T and newValue is T['value']
        filter.value = newValue;
      
        if (isColorFilter(filter)) {
          // Not OK, filter is ColorFilter now but newValue is still Filter['value']
          filter.value = newValue;
        }
      }
      

      在调用onValueChange 时,您仍然会得到正确的验证,您只需要在函数体内进行一些类型转换:(

      【讨论】:

        猜你喜欢
        • 2021-06-09
        • 2021-02-26
        • 1970-01-01
        • 1970-01-01
        • 2018-02-28
        • 2022-10-02
        • 1970-01-01
        • 2020-07-22
        • 2021-12-14
        相关资源
        最近更新 更多