Reputation: 185
You can see in the following code snippet that I had to manually type all possible string options for GIF. But I guess there must be a better way to achieve this. Anybody can help me with this?
type GIF =
| 'dmg_high'
| 'dmg_mid'
| 'dmg_sustained'
| 'quest_invite'
| 'quest_start'
| 'quest_finish'
| 'error'
const tenor = {
TENOR_URL: 'https://tenor.com/view/',
GIFS: {
dmg_high: ['damage-thats-alot-of-damage-jon-tron-gif-13054497'],
dmg_mid: ['hanginthere-damage-gif-19763661'],
dmg_sustained: [
'ugh-guys-im-hit-jason-david-frank-red-zeo-ranger-tommy-oliver-power-rangers-zeo-gif-19564332',
],
quest_invite: ['gandalf-looking-for-adventure-gif-13515313'],
quest_start: [
'adventure-lotr-hobbit-lord-of-gif-5730296',
'lord-of-the-rings-ian-mc-kellen-gandalf-prepare-for-battle-prepare-gif-4879285',
],
quest_finish: ['clapping-clap-applause-lotr-lord-gif-5730286'],
error: 'debugging-we-bare-bears-panda-grizzly-polar-bear-gif-7268856',
},
gif(key: GIF) {
if (!this.GIFS[key]) {
return this.TENOR_URL + this.GIFS.error
}
return this.TENOR_URL + this.GIFS[key][0]
},
}
export default tenor
Upvotes: 0
Views: 64
Reputation: 468
The idea is to define GIFS outside the main object in order to get a type out of its keys:
const TENOR_URL = '...'
const GIFS= {
dmg_high: ['damage-thats-alot-of-damage-jon-tron-gif-13054497'],
dmg_mid: ['hanginthere-damage-gif-19763661'],
dmg_sustained: [
'ugh-guys-im-hit-jason-david-frank-red-zeo-ranger-tommy-oliver-power-rangers-zeo-gif-19564332',
],
quest_invite: ['gandalf-looking-for-adventure-gif-13515313'],
quest_start: [
'adventure-lotr-hobbit-lord-of-gif-5730296',
'lord-of-the-rings-ian-mc-kellen-gandalf-prepare-for-battle-prepare-gif-4879285',
],
quest_finish: ['clapping-clap-applause-lotr-lord-gif-5730286'],
error: 'debugging-we-bare-bears-panda-grizzly-polar-bear-gif-7268856',
}
const gif =(key: keyof typeof GIFS)=>{
if (!GIFS[key]) {
return TENOR_URL + GIFS.error
}
return TENOR_URL + GIFS[key][0]
}
const tenor = {
TENOR_URL,
GIFS,
gif,
}
Upvotes: 0
Reputation: 187034
The fewest changes to your code that would work is probably to define gifs
as a separate constant, and get the keys from it with keyof typeof gifs
.
const gifs = {
dmg_high: ['a'],
dmg_mid: ['b'],
dmg_sustained: ['c'],
quest_invite: ['d'],
quest_start: ['e','f'],
quest_finish: ['g'],
error: 'h',
}
type GIF = keyof typeof gifs
However...
In typescript it's usually best to start from an interface of type, and then create your constants as those types. So you should write those keys twice. Once in the interface (this is your template the ensure you have all the data in the right format), and once in the data (which is checked to make sure it's in the correct format).
This ensures that your runtime data matches your expected type, which is the whole point of typescript.
That might look something like this:
type GIF =
| 'dmg_high'
| 'dmg_mid'
| 'dmg_sustained'
| 'quest_invite'
| 'quest_start'
| 'quest_finish'
interface Tenor {
TENOR_URL: string
GIFS: { [key in GIF]: string[] } & { error: string }
gif(key: GIF): string
}
This also fixes a bug. It's clear that error
is special, since it's a single string, and not an array of strings like the others.
By leaving that key out of GIF
it's not a valid key. This is important because if error
was passed in then your function would execute this.GIFS['error'][0]
which returns the first letter of the error gif string, and not the whole string, which isn't what you want. So we don't allow error
in GIF
but we do have it in the type for the object of gifs as the correct type so it can still be declared and accessed.
And now you can create your object as that type and benefit from type checking on your runtime data:
const tenor: Tenor = {
TENOR_URL: 'https://tenor.com/view/',
GIFS: {
dmg_high: ['a'],
dmg_mid: ['b'],
dmg_sustained: ['c'],
quest_invite: ['d'],
quest_start: ['e','f'],
quest_finish: ['g'],
error: 'h',
},
gif(key: GIF) {
if (!this.GIFS[key]) {
return this.TENOR_URL + this.GIFS.error
}
return this.TENOR_URL + this.GIFS[key][0]
},
}
Upvotes: 2