Here is some code:
declare const foo: Record<string, number>
const x = foo['some-key']
TypeScript says x
has type number
.
It should be number | undefined
, because there's no guarantee that some-key
exists on the object.
Why does TypeScript give this false reassurance, even with strict: true
?
CodePudding user response:
You can enable this behaviour using the noUncheckedIndexedAccess
property ("Add undefined to a type when accessed using an index."), see this playground.
As to why that's not the strict
behavious, I can only make a guess: It mimicks the behaviour of arrays in the default configuration. In essence, an array (at least in regards to accessing its element) can be understood as a Record<int, T>
. In the usual case, you know which elements exist and you can access and TypeScript trusts you in doing so properly (maybe foolishly so).
It actually never occured to me as strange that a Record<string, number>
would not return a number | undefined
on index access, since that's what I told it to be. string
in, number
out. I can see where you are coming from though!
A more definitive answer would probably require either one of the decision makers or active TypeScript maintainer to be sure though.
CodePudding user response:
It should be
number | undefined
, because there's no guarantee that some-key exists on the object.
I don't think this is generally true. I tend to avoid optional fields and having a util that is a little more readable than { [K in MyKeys]: number }
for creating exhaustive object types is quite nice.
There is nothing stopping you from writing Partial<Record<string, number>>
or from creating your own PartialRecord
.
type PartialRecord<K extends PropertyKey, T> = { [P in K]?: T }
I would consider Record
as a type-level macro and nothing more. There is no philosophical meaning to it.
What you wrote is strictly equivalent to writing a simple index signature. It's not where Record
really shines. The following types are exactly the same thing:
type foo = Record<string, number>;
type foo = { [K in string]: number };
type foo = { [k: string]: number };
Record
is more useful when you already have some union type and you want to create an object type out of it:
type MyKeys = 'foo' | 'bar' | 'baz';
type foobar = Record<MyKeys, number>
// same as
type foobar = {
foo: number
bar: number
baz: number
};
TS is not a sound type system. Trying to make sense of it can only be frustrating. If you think of it as an ugly type-level programming language for ugly Javascript, you will be a lot happier ;)