'Why <array>.[n] type are different from <array>.at(n)
I'm trying to switch from using <array>.at() to <array>.[] for consistency. But here is a problem I have run into:
const array: string[] = [];
if (array[0]) {
const item = array[0]; // string
}
if (array.at(0)) {
const item = array.at(0); // string | undefined
}
if (array[0]) {
const item = array.at(0); // string | undefined
}
Why types are different? I'm missing something? I have read about at() method but there not a lot of information about typescript.
Solution 1:[1]
With those if (array[0])-style type guards (and particularly since you've now said you have the noUncheckedIndexedAccess option enabled), arguably the answer is just that TypeScript doesn't narrow types across function calls. After all, in the general case, the function can return a different value on every call (not all functions are pure).
You can lock it in with a const:
// Note: `noUncheckedIndexedAccess` enabled
const array: string[] = [];
if (array[0]) {
const item = array[0];
// ^? ???? string
console.log(array[0]); // string
}
const item1 = array.at(0);
if (item1) {
console.log(item1);
// ^? ???? string
}
But that raises the interesting question of why without the guards are they different (if you don't have noUncheckedIndexedAccess enabled)?
const array: string[] = [];
const i1 = array[0];
// ^? ???? string
const i2 = array.at(0);
// ^? ???? string | undefined
I suspect the reason is twofold:
Long ago, the TypeScript team took the pragmatic decision that (by default¹) indexed access into an array (like a
string[]) should return the array's element type (string), not the array's element type orundefined(string | undefined), even though the latter is a more correct interpretation of property access (which is what indexed access really is) and of the runtime reality that you could indeed getundefinedfrom it. Imagine how awkward TypeScript code would be if they hadn't! Every array access would involve an| undefinedthat would just end up littering the code with non-null assertions or whatever. So they took the pragmatic approach.The
atmethod has much more limited scope than indexed access in general — it's not at all intended to replace indexed access everywhere, and its primary use case is for when indexes are negative. So the types for it reflect the fact that it may returnundefined, because that won't require non-null assertions everywhere — only in places where you useatinstead of indexed access. So there's no argument for not aligning with the spec and it's more useful for the types to closely align with the specification in this case.
¹ Re "by default": You can tell TypeScript you want indexed access to include | undefined via the noUncheckedIndexedAccess option. With that option enabled, my example above becomes:
// With `noUncheckedIndexedAccess` enabled
const array: string[] = [];
const i1 = array[0];
// ^? ???? string | undefined <== Changed
const i2 = array.at(0);
// ^? ???? string | undefined
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
