6
votes

found the code first in http://www.typescriptlang.org/docs/handbook/advanced-types.html#example-1

and then found this similar code in typescript codebase: https://github.com/microsoft/TypeScript/blob/master/tests/cases/conformance/types/conditional/conditionalTypes1.ts#L75

type KnockoutObservable<T> = { object: T };
type KnockoutObservableArray<T> = { array: T };

type KnockedOut<T> = T extends any[] ? 
                       KnockoutObservableArray<T[number]> : 
                       KnockoutObservable<T>;

type KnockedOutObj<T> = {
    [P in keyof T]: KnockedOut<T[P]>;
}

what is the T[number] mean in the code?

in the Typescript Handbook example, it said

the element type of the array as T[number]

while test in the playground(just for testing), replace T[number] with T or T[any] seems no different, but can not replace with T[string](why?).

the [number] after T seems not an index.

2

2 Answers

9
votes

Array is declared using an index signature, something like this:

interface ArrayMaybe<Element> {
    [index: number]: Element;
}

(I call this ArrayMaybe because I haven’t specifically copied Array’s declaration, and it’s not relevant.)

Index signatures indicate that a type can have properties using any value of that type as the key or index, but with the same type of value at each such key/index. So ArrayMaybe<number> can have values at 0, 1, 42, and so on, but wherever it has such properties, the property’s value will be a number.

Coming from the other side of things, when talking about some type T, T[____] references some particular property of T. So { foo: 'bar'; }['foo'] will refer to type 'bar'. In the case of an index signature as we see above, we can use T[number] to refer to the type of that index signature—in the case of ArrayMaybe, that is Element. Which is precisely how it’s being used in your example.

You can’t use T[string] because Array doesn’t have a string index signature. You’re allowed to use those, but Array doesn’t. Since it doesn’t have a string index signature, T[string] isn’t legal. You can use T['length'], though, since Array does have a property with that particularly string. Using string or number refers to any string or number—which requires an index signature.

For an example of a string index signature, consider

interface Dictionary<Value> {
    [key: string]: Value;
}

With this, we can use T[string] when T is some Dictionary—and T[string] will be Value.


All that said, personally, I prefer to use a dedicated ambient type for referring to the element of an array, like so:

type ElementOf<T extends unknown[] | readonly unknown[]> = T[number];

And then from then on you can just use ElementOf<T> instead of T[number]. I find that more readable and clearer than using T[number].

0
votes

Seems to find basic explanation here https://www.typescriptlang.org/docs/handbook/2/conditional-types.html

When Flatten is given an array type, it uses an indexed access with number to fetch out string[]’s element type. Otherwise, it just returns the type it was given.

type Flatten<T> = T extends any[] ? T[number] : T;

// Extracts out the element type

type Str = Flatten<string[]>;
     
type Str = string

// Leaves the type alone.
type Num = Flatten<number>;
     
type Num = number

T[number] will return type of T array element