问题描述
更新:对于所需的行为,TypeScript 需要存在的泛型类型 - 并且好像 TS 4.1 没有它们。感谢有帮助的回答。我认为要解决键入 react-query useQueries
的问题,我们仍然可以在提供 unkNown
时使用 selector
。我会努力让它发挥作用,看看它会去哪里。
考虑以下事项:
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: string;
}]
唉,在 selector: d => d.nested
中,d
也不是 unkNown
类型,而不是 TData
类型。所以我们并没有像希望的那样进行类型推断。
返回类型的伪代码如下所示:
- 对于数组的每个条目:
是否可以通过 TypeScript 中的类型系统来表达这一点? See playground here.
所以这里有两个问题:
-
selector
流经TData
作为输入 - 整体函数的返回类型
解决方法
// 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 可能会帮助您以更好的类型安全方式构建带有回调的数据结构
这些链接可能会帮助您了解这里发生了什么: