TypeScript-从通用联合类型缩小类型

时间:2020-07-02 15:10:07

标签: typescript typescript-generics

//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无法按通用类型区分联合。但是,有没有如我所描述的解决方法?

我想使用与开关盒类似的结构(不只是if-else)

2 个答案:

答案 0 :(得分:0)

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

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

编译器将为Filter推断T,根据通用签名,这是“正确的”,但是会导致与非通用版本相同的问题。函数内部可能是newValuefilter不匹配。 ?


在上述GitHub问题中,存在许多有关解决此问题的方法的建议和讨论。如果是microsoft/TypeScript#27808之类的内容,您可以说类似T extends-exactly-one-member--of Filter之类的内容,这意味着T可以是TickListFilterColorFilter,但不是Filter。但是现在还没有办法这么说。


目前,最简单的操作方法(假设您不想实际检查函数filternewValue是否匹配的实现中)将涉及{{3} }或类似的东西。您需要放松类型检查以允许代码,并希望/希望没有人像我上面那样调用您的函数。

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

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

好的,希望能有所帮助;祝你好运!

type assertions

答案 1 :(得分:0)

如果您想要手动解决方案,可以求助于编写自己的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时仍会得到正确的验证,只需要在函数体内进行某种类型转换:(