This Crazy Syntax Lets You Get An Array Element's Type
Learn how to extract the type of an array element in TypeScript using the powerful Array[number]
trick.
When you're declaring an array type in TypeScript, you've got one of two options: Array<T>
or T[]
.
Dominik (@TKDodo on Twitter), one of the maintainers of React Query, recently posted an article on which option you should choose.
He strongly advocated for Array<T>
, but I think the picture is a little more complex.
Array<T>
and T[]
are functionally identical in your code.const firstTest = (arr : Array <string>) => {};
const secondTest = (arr : string[]) => {};
// Both behave the same!
firstTest (["hello", "world"]);
secondTest (["hello", "world"]);
keyof
with T[]
can lead to unexpected results.type Person = {
id : string;
name : string;
};
const result : keyof Person [] = ["id", "name"];Type 'string[]' is not assignable to type 'keyof Person[]'.2322Type 'string[]' is not assignable to type 'keyof Person[]'.
The fix is to use Array<T>
instead:
const result : Array <keyof Person > = ["id", "name"];
Dominik argues that Array<string>
is more readable than string[]
. This is subjective - it's like the difference between reading "array of strings" or "string array".
When hovering values or displaying errors, TypeScript uses the T[]
syntax. Inexperienced TS devs might experience cognitive load when translating between Array<T>
in their code and T[]
in their errors.
const array = [1, 2];
Overall, I disagree with Dominik that Array<T>
is always the better choice. There are enough caveats to either approach that I won't be making a recommendation one way or the other.
But - you should be consistent. You can use this ESLint rule to enforce one or the other in your codebase. And if I had to choose, I would choose T[]
.
Developers love a syntactical argument - especially when there is little functional difference between the two options.
Array<T>
and T[]
behave exactly the same, as noted above - with one small exception.
type Test1 = [...Array <string>, ... Array < string > ];A rest element cannot follow another rest element.1265A rest element cannot follow another rest element.
type Test2 = [...string[], ... string [] ];A rest element cannot follow another rest element.1265A rest element cannot follow another rest element.
Here, we're getting an error using the T[]
syntax when trying to use it in a rest position. But even this behavior might disappear in a future TS version, as this PR demonstrates.
So, we can treat the two as functionally the same.
keyof
If you're going to make a firm judgment on which syntax to use, you need to consider the keyof
operator.
As described above, keyof
with T[]
can lead to unexpected results.
type Person = {
id : string;
name : string;
};
const result : keyof Person [] = ["id", "name"];Type 'string[]' is not assignable to type 'keyof Person[]'.2322Type 'string[]' is not assignable to type 'keyof Person[]'.
You would think here that keyof Person
would resolve before the []
operator kicks in, meaning you'd end up with a type like ('id' | 'name')[]
.
But unfortunately, the []
resolves first, so you end up performing a keyof
on Person[]
.
You can fix this by wrapping keyof Person
in parentheses:
const result : (keyof Person )[] = ["id", "name"];
Or, you can use Array<T>
instead:
const result : Array <keyof Person > = ["id", "name"];
Dominik argues that Array<T>
is more readable than T[]
. You might agree with this - but I would argue that it's subjective.
I don't want to offer an opinion here - but I want to make sure your opinion is well-informed.
If you want to stay consistent with Array<T>
, you'll probably also want to use the ReadonlyArray<T>
type:
const array : ReadonlyArray <string> = ["hello", "world"];
array .push ("foo");Property 'push' does not exist on type 'readonly string[]'.2339Property 'push' does not exist on type 'readonly string[]'.
We can compare this to the readonly T[]
syntax:
const array2 : readonly string[] = ["hello", "world"];
array2 .push ("foo");Property 'push' does not exist on type 'readonly string[]'.2339Property 'push' does not exist on type 'readonly string[]'.
Which do you prefer? I find this one pretty hard to differentiate.
For handling arrays of arrays, you'll also want to consider Array<Array<T>>
:
const array : Array <Array <string>> = [
["hello", "world"],
["foo", "bar"],
];
We can compare it to the T[][]
syntax:
const array2 : string[][] = [
["hello", "world"],
["foo", "bar"],
];
Which do you prefer?
T[]
TypeScript does offer an opinion on which it prefers. In hovers and errors, TypeScript will always use the T[]
syntax.
const array = [1, 2];
const asConstArray = [1, 2] as const ;
const arrayOfArrays = [ [1, 2],
[3, 4],
];
const stringArray = ["hello", "world"];
const numArray : number[] = stringArray ;Type 'string[]' is not assignable to type 'number[]'.
Type 'string' is not assignable to type 'number'.2322Type 'string[]' is not assignable to type 'number[]'.
Type 'string' is not assignable to type 'number'.
This means that if you're using Array<T>
in your code, less experienced TypeScript developers will experience some cognitive load translating between the two syntaxes.
This is a big reason why, to me at least, T[]
feels more natural - it's more present in the language, encouraged by the compiler, and used everywhere in the docs.
After all of this deep thought, I don't think there's a clear winner here.
I have personally lost hours of my life trying to figure out why keyof T[]
wasn't working as expected.
But I can also see a strong argument that T[]
is the more intuitive choice because of how embedded it is within TypeScript as a whole.
It really comes down to one question: would I reject a PR containing the one we didn't use? No.
Use whichever you like. Use a linter to stay consistent. And don't worry about it too much.
Share this article with your friends
Learn how to extract the type of an array element in TypeScript using the powerful Array[number]
trick.
Learn how to publish a package to npm with a complete setup including, TypeScript, Prettier, Vitest, GitHub Actions, and versioning with Changesets.
Enums in TypeScript can be confusing, with differences between numeric and string enums causing unexpected behaviors.
Is TypeScript just a linter? No, but yes.
It's a massive ship day. We're launching a free TypeScript book, new course, giveaway, price cut, and sale.
Learn why the order you specify object properties in TypeScript matters and how it can affect type inference in your functions.