最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Index signatures in TypeScript don't handle undefined - Stack Overflow

programmeradmin0浏览0评论

I think I basically got how index signatures work in TypeScript. However, there is one thing I don't get. Given the following sample code:

const sales: {[ key: string ]: number } = {
  a: 532,
  b: 798,
  c: 264
};

console.log(sales.a);
console.log(sales.d);

Now the compiler says, sales.a and sales.d are of type number. But shouldn't that be number | undefined, since the compiler can not know if a and / or d are actually there?

I can't come up with a specific interface here, because a and d are arbitrarily chosen at runtime, and not predefined.

I can solve this by using

{[ key: string ]: number | undefined }

as a type, but this seems to be cumbersome and annoying (and a pretty lame solution for a pretty trivial problem, given how objects are typically used in JavaScript).

So, one could say: To me, this seems counterintuitive: What sense do index signatures have, if they virtually enforce that for every possible string on earth there is also a value? For which type is this ever true?

Is there actually no easier solution for this than to come up with a

{[ …: string ]: … | undefined }

type over and over again? Nothing integrated into the language?

It's hard to believe that, given the fact that there are specialties such as Partial<T> and co. … any thoughts on this?

PS: Is Record<T> (semantically) the same as {[ key: string ]: T | undefined }?

I think I basically got how index signatures work in TypeScript. However, there is one thing I don't get. Given the following sample code:

const sales: {[ key: string ]: number } = {
  a: 532,
  b: 798,
  c: 264
};

console.log(sales.a);
console.log(sales.d);

Now the compiler says, sales.a and sales.d are of type number. But shouldn't that be number | undefined, since the compiler can not know if a and / or d are actually there?

I can't come up with a specific interface here, because a and d are arbitrarily chosen at runtime, and not predefined.

I can solve this by using

{[ key: string ]: number | undefined }

as a type, but this seems to be cumbersome and annoying (and a pretty lame solution for a pretty trivial problem, given how objects are typically used in JavaScript).

So, one could say: To me, this seems counterintuitive: What sense do index signatures have, if they virtually enforce that for every possible string on earth there is also a value? For which type is this ever true?

Is there actually no easier solution for this than to come up with a

{[ …: string ]: … | undefined }

type over and over again? Nothing integrated into the language?

It's hard to believe that, given the fact that there are specialties such as Partial<T> and co. … any thoughts on this?

PS: Is Record<T> (semantically) the same as {[ key: string ]: T | undefined }?

Share Improve this question edited Jun 16, 2022 at 15:06 jcalz 329k29 gold badges437 silver badges441 bronze badges asked Aug 3, 2019 at 11:06 Golo RodenGolo Roden 151k101 gold badges314 silver badges441 bronze badges
Add a comment  | 

2 Answers 2

Reset to default 15

UPDATE FOR TYPESCRIPT 4.1+

There is now a --noUncheckedIndexedAccess compiler option which, when enabled, includes undefined when reading from index signature properties. This option is not part of the --strict suite of compiler options, because it's too much of a breaking change and some of the workarounds are annoying.


PRE-TS4.1 ANSWER:

This has been raised as a suggestion (see microsoft/TypeScript#13778) and has essentially been declined (although it is listed as "awaiting more feedback") because it is expected to cause more bugs than it would fix. You're right that the current situation is inconsistent and technically incorrect/unsound, but it is not one of TypeScript's goals to "apply a sound or 'provably correct' type system. Instead, [it should] strike a balance between correctness and productivity."

TypeScript doesn't actually enforce every possible key matching the index to be present (so index signature properties are effectively optional), but it does enforce that any such key which is present has a defined value of the right type... this is one of the few places in TypeScript where missing and undefined are distinguished.

As you noted, if you want your own index-signature types to behave the same as other optional properties (meaning that undefined is automatically a possible value), you can add | undefined to the property type. If you want existing index-signature types to behave this way (like Array), you'll have to fork your own copy of the standard library and do it for yourself. They won't do it upstream because it would make lots of people very sad to deal with this.

If you really want to see this changed, you might want to visit the GitHub issue and comment or upvote, but I wouldn't hold my breath... your time is probably better spent moving past this (I speak from experience... I've had to do this several times when dealing with TypeScript's pragmatic inconsistencies.)

I hope that helps. Good luck!

This is just an addition to the accepted answer, because it's totally right

If your aim is to get compile time errors if you don't check if e.g. sales.d exists/is not undefined, you could implement your own interface here:

interface SomeDictionary<T> {
  {[ key: string ]: T | undefined }
}

const sales: SomeDictionary<number> = {
  a: 532,
  b: 798,
  c: 264
};

// compile time error
const result = sales.a + sales.b;

// working
if(sales.a !== undefined && sales.b !== undefined){
  const result = sales.a + sales.b;
}

AFAIK there is no such built-in interface in typescript.

I think index signatures (in your case) make sense if you want to iterate over the keys of an such an object:

const sales: {[ key: string ]: number } = {
  a: 532,
  b: 798,
  c: 264
};

let sum = 0;
for(const key in Object.keys(sales)){
  sum = sum + sales[key];
}

I assume there are much more uses cases which are not coming into my mind right now..

To your side question: No, it's not (if you meant Record from here. Record<T> does not even compile, because it needs a second type argument. I would say Record's are not really related to your issue.

发布评论

评论列表(0)

  1. 暂无评论