Element implicitly has an 'any' type because expression of type 'string' can't be used to index type - Phaser

末鹿安然 提交于 2021-01-07 01:35:56

问题


I know this has been asked, but still can't figure out a solution for my case.

I have a file where I define certain values and then I was to loop thru them. I get the error in this part of the code

preloadImages(){
    this.load.setPath("./assets/images");
    for (const key in STATIC.IMAGES) {
        if (STATIC.IMAGES.hasOwnProperty(key)) {
            this.load.image(STATIC.IMAGES[key], STATIC.IMAGES[key]);                
        }
    }
}

The complain comes on the STATIC.IMAGES[key] calls. And STATIC comes from a file with

export const STATIC = {

SCENES: {
    LOAD: "LOAD",
    MENU: "MENU"
},

IMAGES: {
    MENU_BG :"bg.png",
    MENU_TITLE: "title.png",
    MENU_PLAY: "play.png",
    MENU_SETTINGS: "settings.png",
    MENU_CREDITS: "credits.png",
    MENU_SPEAKER: "speaker.png",
    MENU_SPEAKER_MUTE: "speaker_mute.png"
},

SPRITES: {
    LOOP: { 
        name: "loop.png",
        size: 64
    }
},

AUDIOS: {
    BG_MUSIC: "airtone.mp3",
    POP: "pop.mp3",
    WOOSH: "woosh.mp3"
}};

Not sure I understand why this happens and how to fix it.


回答1:


Let's pare that down into a minimal, reproducible example. Feel free to replace obj below with your STATIC.IMAGES; it's the same explanation either way:

let obj = { a: "hey", b: "you", c: "guys" };

for (const k in obj) {
  console.log(obj[k].toUpperCase()); // error!
  /* Element implicitly has an 'any' type because expression of type 
  'string' can't be used to index type '{ a: string; b: string; c: string; }'. */
}

Here we have an object obj with three keys. We try to iterate over those keys with a for..in loop and we get an error that you can't use k to index into obj. Why? Isn't that the point of for..in loops?


The problem here is that object types in TypeScript are open or extendible, and not closed or exact. You can add extra properties to an object in TypeScript without violating its type. This is great in some situations: it allows you to extend interfaces and subclass classes by adding properties. But in other situations it's a pain:

The compiler sees obj as a value of type {a: string, b: string, c: string}. Since this type is not exact, when you do for (const k in obj), the compiler knows that k will take on values "a", "b", and "c", but it doesn't know that those are the only possible values:

let hmm = { a: "hey", b: "you", c: "guys", d: 12345 };
obj = hmm; // no error!

There's no error in obj = hmm because the value hmm matches {a: string, b: string, c: string}. The d property does not violate the type. So, for all the compiler knows, for (const k in obj) will enumerate "a", "b", "c", and who knows what other string values. Therefore, the type of k is not keyof typeof obj. It is just string.

And it's not safe to index into {a: string, b: string, c: string} with a key of type string. Indeed, with hmm assigned to obj, there will be a runtime error calling (12345).toUpperCase(). So it's correct, if not very useful, for the compiler to warn you.

See microsoft/TypeScript#35847 for an "official" version of the answer to this question. Also see Why doesn't Object.keys() return a keyof type in TypeScript?, which is the same question except using Object.keys(obj).forEach(k => ...) instead of for (const k in obj) {...}... and it has the same answer.

If exact types are ever introduced, then maybe this problem would just go away. There is a feature request for this: microsoft/TypeScript#12936. But it doesn't look like this is going to happen any time soon.


For now, then, what can be done? Well, if we know for sure that obj won't have any extra keys that the compiler doesn't know about, we can treat it as if it were an exact type and just tell the compiler not to worry about it. This "telling the compiler" takes the form of a type assertion:

for (const k in obj) {
  console.log(obj[k as keyof typeof obj].toUpperCase()); // okay
}

Here, k as keyof typeof obj is the type assertion. We have to be careful not to lie to the compiler this way, but as long as we are careful, then it's fine.


For completeness you can also declare k outside the loop and give it a narrower type; this is equivalent to the type assertion in terms of type safety:

let k: keyof typeof obj;
for (k in obj) {
  console.log(obj[k as keyof typeof obj].toUpperCase()); // okay
}

Okay, hope that helps; good luck!

Playground link to code



来源:https://stackoverflow.com/questions/62377614/element-implicitly-has-an-any-type-because-expression-of-type-string-cant-b

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!