网站搜索

typescript - 精炼元组的联合类型


我正在尝试优化一种类型,它要么是这两个元组中的一个,但我无法让 TypeScript 配合。

declare const foo: [string, string] | [number, number];
declare function isNumber(x: unknown): x is number;

if (isNumber(foo[0])) {
  foo; // not refined
}

这可能吗?ts playground

答案#1

在 microsoft/TypeScript#42384 上有一个功能请求,希望在缩小属性类型时也能缩小父对象类型。目前,TypeScript 仅在检查其判别属性时过滤判别联合类型。但是,虽然类型 [number, number] | [string, string] 是一个联合类型,它并不被视为 判别联合类型,因为 number 和 string 都不是有效的 判别值。判别属性的类型需要具有字面量类型,例如 "stringLiteral" 或 123。number 和 string 类型太广泛了。

在 microsoft/TypeScript#42384 实现之前,你需要找到解决方法。如果你愿意,你可以编写自己的自定义类型保护函数,该函数接受一个联合类型的对象、一个键名,以及该键处属性的类型保护,并使用它们来区分联合类型:

// discriminate union T to members with a value of type V at key K
function hasPropOfType<T, K extends keyof T, V extends T[K]>(
    obj: T, k: K, guard: (x: T[K]) => x is V): obj is Extract<T, Record<K, V>> {
    return guard(obj[k])
}

然后你可以写 hasPropOfType(foo, 0, isNumber) 来代替 isNumber(foo[0]):

if (hasPropOfType(foo, 0, isNumber)) {
    foo;
    //^? const foo: [number, number]
    foo[1].toFixed()
} else {
    foo;
    //^? const foo: [string, string]
    foo[1].toUpperCase()
}

代码的 Playground 链接

答案#2

TS 不会跟踪元组成员之间的相关性,我建议使用一些通用方法:

操场

type DoubleTuple<T> = [T, T];
declare const foo: DoubleTuple<number> | DoubleTuple<string>;
type Types = {string: string, number: number};
declare function isDoubleTupleOfType<T extends keyof Types>(x: DoubleTuple<any>, type: T): x is DoubleTuple<Types[T]>;

if (isDoubleTupleOfType(foo, 'number')) {
  foo; 
  //^?
  const n = foo[0];
  //    ^?
}