TypeScript:使用可变元组类型进行依赖类型推断

Posted

技术标签:

【中文标题】TypeScript:使用可变元组类型进行依赖类型推断【英文标题】:TypeScript: dependant type inference with variadic tuple types 【发布时间】:2021-04-15 02:17:11 【问题描述】:

更新:对于所需的行为,TypeScript 需要存在的泛型类型 - 就好像 TS 4.1 它没有它们一样。感谢有用的答案。我认为要解决输入 react-query useQueries 的问题,仍然有一种方法可以在提供 selector 时使用 unknown。我会努力让它发挥作用,看看结果如何。

考虑以下几点:

interface Data<TData = unknown, TSelected = unknown> 
    data: TData;
    selector?: (data: TData) => TSelected


function makeArrayAsConstItemsForDataTypesOnly<
    TItem extends readonly Data[]
>(arr: [...TItem]):  [K in keyof TItem]:  item: Extract<TItem[K], Data>["data"]   
    return arr.map(item => 
        return item.selector 
            ?  item: item.selector(item.data) 
            :  item: item.data 
    ) as any;


const returnedData = makeArrayAsConstItemsForDataTypesOnly([
     data:  nested: 'thing' , selector: d => d.nested ,
     data: 1 ,
     data: 'two' ])

returnedData 取类型:

const returnedData: [
    item: 
        nested: string;
    ;
, 
    item: number;
, 
    item: string;
]

selector 可能会或可能不会随每个元素一起提供。如果提供,它会映射提供的 data 类型并转换返回的数据。

鉴于上述示例,理想情况下返回的类型为:

const returnedData: [
    item: string;
, 
    item: number;
, 
    item: string;
]

唉,在 selector: d =&gt; d.nested 中,d 也不是采用 unknown 类型,而不是 TData 类型。所以我们没有像希望的那样进行类型推断。

返回类型的伪代码如下所示:

对于数组的每个条目: 获取data 属性 如果数组条目包含selector,则返回 item: entry.selector(entry.data) 否则返回 item: entry.data

是否可以通过 TypeScript 中的类型系统来表达这一点? See playground here.

所以这里有两个问题:

selector 流经TData 作为输入 整个函数的返回类型

【问题讨论】:

只有d.nested有问题吗? 否 - 有 2 个问题: 1. 在 selector: d =&gt; d.nested 中,d 的类型为 unknown,而不是 TData。 2.问题中陈述的问题。为了清楚起见,我可能应该对其进行编辑 - 这就是你的意思吗? 已编辑并在上面提到 【参考方案1】:
// credits goes to https://***.com/questions/50374908/transform-union-type-to-intersection-type/50375286#50375286
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
  k: infer I
) => void
  ? I
  : never;

//credits goes to https://github.com/microsoft/TypeScript/issues/13298#issuecomment-468114901
type UnionToOvlds<U> = UnionToIntersection<
  U extends any ? (f: U) => void : never
>;

//credits goes to https://github.com/microsoft/TypeScript/issues/13298#issuecomment-468114901
type PopUnion<U> = UnionToOvlds<U> extends (a: infer A) => void ? A : never;

//credits goes tohttps://***.com/questions/53953814/typescript-check-if-a-type-is-a-union#comment-94748994
type IsUnion<T> = [T] extends [UnionToIntersection<T>] ? false : true;

type UnionToArray<T, A extends unknown[] = []> = IsUnion<T> extends true
  ? UnionToArray<Exclude<T, PopUnion<T>>, [PopUnion<T>, ...A]>
  : [T, ...A];

type Values<T> = T[keyof T]
type MapPredicate<T> =  item: Values<T> ;

// http://catchts.com/tuples
type MapArray<
  Arr extends ReadonlyArray<unknown>,
  Result extends unknown[] = []
  > = Arr extends []
  ? Result
  : Arr extends [infer H]
  ? [...Result, MapPredicate<H>]
  : Arr extends readonly [infer H, ...infer Tail]
  ? MapArray<Tail, [...Result, MapPredicate<H>]>
  : never;

type Test1 = MapArray<[nested:42,a:'hello']>[0] //  item: 42; 

interface Data<TData = any, TSelected = any> 
  data: TData;
  selector?: (data: TData) => TSelected


const builder = <T, R>(data: T, selector?: (data: T) => R): Data<T, R> => (
  data,
  selector
)

type Mapper<T extends Data> = T['selector'] extends (...args: any[]) => any ? ReturnType<T['selector']> : T['data']

const first = builder( nested: 'thing' , d => d.nested);
const second = builder( a: 42 );

type First = typeof first
type Second = typeof second

type Result = Mapper<First>

const arr = [first, second];

function makeArrayAsConstItemsForDataTypesOnly<T extends Data>(data: Array<T>) 
  const result = data.map((item) => 
    return item.selector
      ?  item: item.selector(item.data) 
      :  item: item.data 
  )

  /**
   * I don't know how to avoid type casting here
   * I tried different approaches, but none of them
   * helped
   */
  return result as MapArray<UnionToArray<Mapper<T>>>


const test = makeArrayAsConstItemsForDataTypesOnly(arr)

type ResultArray = typeof test;

type FirstElement = ResultArray[0] //  item: string 
type SecondElement = ResultArray[1] //  item: number 

我知道,使用类型转换不是最好的解决方案,但我无法以更好的方式推断泛型。

This answer 可能会帮助您以更好的类型安全方式构建带有回调的数据结构

这些链接可能会帮助您了解这里发生了什么:

PopUnion,UnionToOvlds UnionToIntersection IsUnion MapArray

【讨论】:

感谢@captain-yossarian 分享您的答案。不幸的是,在我的情况下,混合使用 builder 函数(我认为它只是为了提供一种类型)不适用于我的场景。对于上下文,我正在尝试强烈键入 react-query 的 useQueries 钩子,因此我不太可能显着更改 API。您可以在此 PR 上查看上下文:github.com/tannerlinsley/react-query/pull/… 顺便说一句,我想说在这样的函数中有限地使用类型断言是可以的——实际上可能是不可避免的 好的,我已经阅读了链接的问题,我想我要求的是语言还不支持的东西——本质上它需要存在的泛型类型。这就是builder 所实现的。这对于 react-query 用例永远不会起作用。我想我需要改变这个问题。我将在上面添加一个编辑。 @JohnReilly,是的,builder 函数仅用于打字。这是一个开销,但我相信 V8 会内联它。不幸的是,在上述情况下推断回调参数的泛型并不容易。我尝试使用其他数据结构,但没有取得重大成功。顺便说一句,问题很好)

以上是关于TypeScript:使用可变元组类型进行依赖类型推断的主要内容,如果未能解决你的问题,请参考以下文章

Python--基本的对象类型(元组_不可变的数据类型)

python学习之数据类型—元组tuple

TypeScript 按任意类型过滤元组类型

数据类型-元组

TypeScript(13): 元组

Python:元组类型