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 => 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 => 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:使用可变元组类型进行依赖类型推断的主要内容,如果未能解决你的问题,请参考以下文章