【问题标题】:TypeScript: dependant type inference with variadic tuple typesTypeScript:使用可变元组类型进行依赖类型推断
【发布时间】: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.问题中陈述的问题。为了清楚起见,我可能应该对其进行编辑 - 这就是你的意思吗?
  • 已编辑并在上面提到

标签: typescript types tuples variadic conditional-types


【解决方案1】:
// credits goes to https://stackoverflow.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://stackoverflow.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 可能会帮助您以更好的类型安全方式构建带有回调的数据结构

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

【讨论】:

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