I am creating a data-file that looks like this:
interface ISprite {
textureName: string,
frame: Frame,
origin: Vec2,
zIndex?: number
}
export let sprites: Record<string, ISprite> = {
monster: {
textureName: "monster",
frame: new Frame(0, 0, 32, 41),
origin: new Vec2(16, 28),
zIndex: -1
},
player: {
textureName: "player",
frame: new Frame(0, 0, 32, 32),
origin: new Vec2(15, 32)
}
};
If I then try to import this data file from another file like so:
import { sprites } from "../data/sprites";
And then try to access a property like this:
let player = sprites.player;
Then I don't get Intellisense (code completion) when I type sprites.
I noticed, however, that if I remove the Record<string, ISprite>
annotation from the sprites
variable declaration that I do get intellisense.
However, I believe I require this annotation, because one of my functions only takes ISprite
types, and I don't want to instead make it take an any
.
Is it possible to maintain the strong typing while also having code completion?
It does that because Record<string, ISprite>
can have any string as a key. The UI can't hint every string.
You can give the UI further information to make it show monster
and player
for auto-completion by using an intersection between a type with those named properties and Record<string, ISprite>
(but keep reading, you may not need to):
export let sprites: {monster: ISprite, player: ISprite} & Record<string, ISprite> = {
// −−−−−−−−−−−−−−−−−^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
monster: {
textureName: "monster",
frame: new Frame(0, 0, 32, 41),
origin: new Vec2(16, 28),
zIndex: -1
},
player: {
textureName: "player",
frame: new Frame(0, 0, 32, 32),
origin: new Vec2(15, 32)
}
};
However:
I believe I require this annotation, because one of my functions only takes ISprite types.
That's fine. Without any type annotation at all, the values your object has on its monster
and player
properties are compatible with ISprite
, so you can pass them to a function expecting ISprite
. TypeScript's type system is structural (based on shapes of objects), not nominal (based on names of types), so it's happy to accept any object as an ISprite
as long as it has all the necessary properties with compatible types. This works just fine, for instance:
export let sprites = {
monster: {
textureName: "monster",
frame: new Frame(0, 0, 32, 41),
origin: new Vec2(16, 28),
zIndex: -1
},
player: {
textureName: "player",
frame: new Frame(0, 0, 32, 32),
origin: new Vec2(15, 32)
}
};
function example(sprite: ISprite) {
console.log(sprite);
}
example(sprites.monster); // <=== Perfectly happy
If you want the type name, or hinting for the members of ISprite
as you're writing futher entries in sprites
, you could give yourself a function:
makeSprite(sprite: ISprite) {
return sprite;
}
It looks like a do-nothing, and it is — at runtime. (Don't worry, it's not expensive). But at authoring time, it lets you do this:
export let sprites = {
monster: makeSprite({
textureName: "monster",
frame: new Frame(0, 0, 32, 41),
origin: new Vec2(16, 28),
zIndex: -1
}),
player: makeSprite({
textureName: "player",
frame: new Frame(0, 0, 32, 32),
origin: new Vec2(15, 32)
})
};
As you're adding entries, you'll be prompted with the property names from ISprite
while you're writing the sprite argument for makeSprite
.