2

I've following Typescript code and I can't really make sense of last line of code

const tuple = <T extends string[]>(...args: T) => args;
const ButtonTypes = tuple('default', 'primary', 'ghost', 'dashed', 'danger', 'link');
export type ButtonType = (typeof ButtonTypes)[number];

This is the line I'm talking to be more explicit.

(typeof ButtonTypes)[number]

Update:

This follows directly from Ways to get string literal type of array values without enum overhead

HalfWebDev
  • 7,022
  • 12
  • 65
  • 103
  • `typeof ButtonTypes` resolves to a type of the variable (array of strings). `[number]` means effectively: "select every type from the array". – zerkms Jul 05 '19 at 06:03
  • `typeof ButtonTypes` this is fine as it's javascript. What would be "[number] means effectively: "select every type from the array"" – HalfWebDev Jul 05 '19 at 06:05
  • Not exactly - `typeof ButtonTypes` means something different when Typescript knows it's trying to evaluate a *type* compared to when it's trying to evaluate an *expression* (like in standard JS). Here, it's trying to evaluate a type, so `typeof ButtonTypes` does *not* turn into `'object'`. – CertainPerformance Jul 05 '19 at 06:08

2 Answers2

3

tuple is a function that takes arguments, and returns an array of all arguments. For example, tuple('foo', 'bar') will evaluate to ['foo', 'bar'].

With

const ButtonTypes = tuple('default', 'primary', 'ghost', 'dashed', 'danger', 'link');

you'll get an array of all those strings:

const ButtonTypes = ['default', 'primary', 'ghost', 'dashed', 'danger', 'link'];

Then, with (typeof foo)[number], you'll retrieve all properties on the variable foo which can be accessed with a numeric index. So, it's just like doing the following:

export type ButtonType = "default" | "primary" | "ghost" | "dashed" | "danger" | "link";

only less repetitive.

CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • 1
    I'm not sure if I fathom this completely. `(typeof foo)[number]` compiles down to `array[number] ` ? Is that correct? If so, don't we normally do `string []` to specify . the array with a type – HalfWebDev Jul 05 '19 at 06:10
  • No, because Typescript is trying to evaluate a *type* here, rather than a normal Javascript expression. (and the `typeof` an array is `object`, not `array`) – CertainPerformance Jul 05 '19 at 06:11
  • How am I getting an array of all strings assigned to `ButtonTypes` as the function is just returning `args` ? – HalfWebDev Jul 05 '19 at 06:24
  • You called `tuple` with a bunch of arguments and assigned the result to `ButtonTypes`, and Typescript is smart enough to understand that `ButtonTypes` is now an array composed of those arguments. So, if you just take the `typeof` that array in a type context, you'll get the type of the array, and the `[number]` will turn that into a union type of all arguments initially passed to `tuple`. – CertainPerformance Jul 05 '19 at 06:28
  • By `Typescript is smart` enough you mean `` will cast args into string array and thus return the array. Correct? – HalfWebDev Jul 05 '19 at 06:29
  • That's only for getting the typings correct - if you're wondering about how the array is gotten out of it, `(...args` uses rest parameter syntax to gather all parameters into an array, just like in standard Javascript, so, eg, Typescript will know (at compile time) that `tuple('foo', 'bar')` will result in `['foo', 'bar']` – CertainPerformance Jul 05 '19 at 06:32
  • Yes. Missed that. But let me ask me this which will make clear hopefully. What if I don't specify an index signature ? That is, what if I do `(typeof ButtonTypes)[]` instead of `(typeof ButtonTypes)[number]`? – HalfWebDev Jul 05 '19 at 06:44
  • Then you would be specifying a type which is an *array* of `ButtonTypes`, eg `const manyButtonTypes: ButtonType = [ButtonTypes, ButtonTypes];` (which isn't what you want, of course) – CertainPerformance Jul 05 '19 at 06:48
  • For some reason it's seeming strange due to the fact that we don't access objects with number index normally in Javascript. And since the value of `ButtonType ` is `object[number]` it is just not clear what's going on here. Had it been `array[number]` would still have something to reason about the index signature. That I'm restricting the access of the array to only number indexes which is the default anyway – HalfWebDev Jul 05 '19 at 06:49
  • We do often access arrays with numeric indicies - in fact, you rarely see anything else. `arr[3]`. It's less common with objects, but we're dealing with an array here – CertainPerformance Jul 05 '19 at 06:51
  • M saying exactly the same. That we are dealing with object here and we do often deal with arrays that way – HalfWebDev Jul 05 '19 at 06:53
  • Yeah, it's somewhat confusing, because the `[]` syntax is overloaded (1: Javascript property accessor 2: Typescript index signature 3. Typescript array type notation) – CertainPerformance Jul 05 '19 at 06:56
  • For example I'm going to use this type as `export interface IBaseButtonProps { type?: ButtonType; ...} `and then use this as ``. Basically I'm specifiying a string. How does `ButtonType` resolves this? Let me start that process of drilling down. ButtonType according to our discussion is `object[number]`. My processing is blocked here. See what I'm trying to understand and how to map. – HalfWebDev Jul 05 '19 at 06:59
  • `ButtonType` is just `"default" | "primary" | "ghost" | "dashed" | "danger" | "link"`, so the `{ type?: ButtonType; ...}` means that a `type` property, if it exists, needs to be one of those strings. – CertainPerformance Jul 05 '19 at 07:03
  • Anyway, I'll try thinking that way and sticking to it. I get it on surface for sure. Still confused about index type signature as @Foydor mentioned. – HalfWebDev Jul 05 '19 at 07:06
2

Line (typeof ButtonTypes)[number] combined of following parts:

  1. typeof ButtonTypes extract type of constant ButtonTypes. Type of ButtonTypes is tuple.

  2. Square braces [] after type means indexed access operator. It extracts type of property from object type (it's all about types). In this case, object type is typeof ButtonTypes which is tuple.

  3. In square braces there is number which gets type of index signature. As tuple is essentially array, it has number index signature. And as tuple consists of elements of different types, (typeof ButtonTypes)[number] resolved to union type "default" | "primary" | "ghost" | "dashed" | "danger" | "link"

And several examples to understand all this staff :-)

This is type

interface I1 {
    prop1: string;
    func2: (a: string) => void;
}

This is constant of type I1

const c1: I1 = {/*...*/}

typeof c1 is I1.

Indexed access operator for I1 will get types of properties. For example

type s = I1['prop1']; // Type s is string
type f = I1['func2']; // Type f is function

And to get type of index signature, we should have type with index signature. So create one

interface I2 {
    [key: string]: string;
}

Now

type s1 = I2[string]; // type of s1 is string
let str: s1 = "some string"; // This is allowed, as type of s1 is string

Arrays and tuples has index signature of type number so

let a = [1, 2, 3];
type ta = (typeof a)[number];  // type ta is number
Fyodor Yemelyanenko
  • 11,264
  • 1
  • 30
  • 38