How to get Type of Array in Typescript generics

后端 未结 5 1919
半阙折子戏
半阙折子戏 2020-12-18 06:56

I have a method like this:

public select(fieldName: keyof TType)

Where TType could be an array type. In case of an array type

相关标签:
5条回答
  • 2020-12-18 07:08

    Using TType[0] as the type is working for me in practice. So, in your example:

    public select(fieldName: keyof TType[0])
    

    As ugly as that is, it seems to work!

    0 讨论(0)
  • 2020-12-18 07:09

    Fist of why do you use the operator keyof ? that leads to an array in each case since fieldName will be only the keys in the TType type.

    If you want an array of User you need to define TType as such:

    type TType = User[]
    ...
    public select(fieldName: TType) {
       ...
    }
    

    If you wish TType to be any array you will define it as such type TType = any[]

    but in the second defintion typescript won't be able to infer the inner type (in this case User which is not what you want i presume.

    Finally if TType could be multiple type of arrays: type TType = User[] | Whatever[] | ...

    If you want a more detail answer please provides a better explanation of what you want.
    In the meantime i hope this helps ;)
    Seb

    0 讨论(0)
  • 2020-12-18 07:12

    One can determine the type parameter T from an array of type type U = T[] by indexing by number, eg., U[number].

    type Foo = {"foo": string};
    type Bar = {"bar": number};
    type FooArray = Foo[];
    type BarArray = Bar[];
    
    FooArray[number]; // {"foo": string}
    BarArray[number]; // {"bar": number}
    

    Here's a TypeScript Playground with the above example.

    0 讨论(0)
  • 2020-12-18 07:21

    You will need a little helper to extract the boxed type:

    type Unboxed<T> =
        T extends (infer U)[]
            ? U
            : T;
    

    Then your method can look like this:

    interface User {
        id: symbol;
        name: string;
    }
    
    class Foo {
        select(fieldName: keyof Unboxed<User[]>) {
            console.log(fieldName) // "id" | "name"
        }
    }
    

    As to your extra question: yes, it's possible, but it may feel a bit strange.

    class Foo {
        select<T extends any[]>(fieldName: keyof Unboxed<T>) {
            console.log(fieldName)
        }
    }
    
    new Foo()
      .select<Window[]>('addEventListener')
    

    Type parameters are meant to describe the arguments living inside the method or the generic type of the class. So perhaps you wanted to do the following:

    class Foo<T extends any[]> {
        select(fieldName: keyof Unboxed<T>) {
            console.log(fieldName)
        }
    }
    
    new Foo<Window[]>()
      .select('addEventListener')
    
    0 讨论(0)
  • 2020-12-18 07:22

    You can definitely make a conditional type function that unwraps an array type up to one level deep, and then use keyof on the result of that. For example:

    // unwrap up to one level
    type Unarray<T> = T extends Array<infer U> ? U : T;
    
    // your class maybe
    declare class Thingy<T> {
      constructor(t: T);
      public select(fieldName: keyof Unarray<T>): void;
    }
    // your interface maybe
    interface User {
      name: string,
      age: number
    }
    
    declare const u1: User;
    declare const u2: User;
    const x = new Thingy(u1);
    x.select("name"); // okay
    const y = new Thingy([u1, u2]);
    y.select("age"); // okay
    y.select("push"); // error
    

    That should work as you want, for the typings, I think. Obviously you also need to have an implementation which works (and note that conditional types in implementations usually require some type assertions or overloads to make the compiler happy... but you seem to be asking about the typings, not the implementation).


    As for your extra question, yes, you can restrict T to just array types, as follows:

    // your class maybe
    declare class Thingy<T extends Array<any>> {
      constructor(t: T);
      public select(fieldName: keyof (T[number])): void;
    }
    // your interface maybe
    interface User {
      name: string,
      age: number
    }
    
    declare const u1: User;
    declare const u2: User;
    const x = new Thingy(u1); // error
    const y = new Thingy([u1, u2]);
    y.select("age"); // okay
    

    Note that I did away with the conditional types altogether here because it's more straightforward.


    Hope that helps; good luck!

    0 讨论(0)
提交回复
热议问题