Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
154 views
in Technique[技术] by (71.8m points)

TypeScript: dependant type inference with variadic tuple types

Update: it looks like for the behaviour desired, TypeScript requires existential generic types - and as if TS 4.1 it doesn't have them. Thanks the helpful answer. I think to solve the typing react-query useQueries there is still a way forward whereby we use unknown when selector is supplied. I'll try and make that work and see where it goes.

Consider the following:

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 takes the type:

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

A selector may or may not be supplied with each element. If supplied, it maps over the supplied data type and transforms the returned data.

Given the above example, then ideally the returned type would be:

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

Alas it isn't, also, in selector: d => d.nested, d takes the type unknown as opposed to the type TData. So we aren't getting the type inference flowing through as hoped.

Pseudo-code for the return type would look like this:

  • for each entry of the array:
    • get the data property
    • if the array entry contains a selector then return { item: entry.selector(entry.data) }
    • else return { item: entry.data }

Is it possible to express this via the type system in TypeScript? See playground here.

So there's two problems here:

  • selector flowing through TData as the input
  • the return type of the overall function
question from:https://stackoverflow.com/questions/65644828/typescript-dependant-type-inference-with-variadic-tuple-types

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)
// 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 }

I know, using type casting is not the best solution, but I was unable to infer generics in better way.

This answer might help you to build data structures with callback in a better type safe way

These links might help you to understand what's goin on here:


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

2.1m questions

2.1m answers

60 comments

57.0k users

...