I have a method like this:
public select(fieldName: keyof TType)
Where TType
could be an array type. In case of an array type
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!
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
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.
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')
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!