typescript - Como afirmar que um valor é uma chave válida sem conhecer as chaves do objeto em tempo de compilação?
No TypeScript, existe alguma forma de declarar que uma variável é um keyof de algum Record sem saber quais são as chaves do Record?
Assuma que uma resposta de API retorna um payload em JSON. Quero definir um tipo para as chaves do payload para que eu possa percorrer o payload e saber que o valor não será indefinido. Isso é possível ou eu simplesmente tenho que verificar se é indefinido toda vez que eu indexo o objeto payload?
Exemplo :
const payload: Record<string, ValidValue> = await myApiCall()
const prop1 = 'a'
const prop2 = 'b'
if (isValidKey(prop1, payload)) {
const value1 = payload[prop1] // the type of `value1` should evaluate to `ValidValue`
}
const value2 = payload[prop2] // the type of `value2` should evaluate to `ValidValue | undefined`
Resposta #1
Acho que encontrei uma solução. Isso parece funcionar como esperado no playground do TS.
const myMap = [1,2,3].reduce((p, n) => ({ ...p, [Math.random()]: n }), {})
type MyMap = typeof myMap
console.log('myMap', myMap)
function isKnownKey(key: string, thing: MyMap): key is keyof MyMap {
return Object.keys(thing).includes(key)
}
const knownTypes = ['a', 'b']
const prop1 = Object.keys(myMap)[0]
const prop2 = 'b'
if (isKnownKey(prop1, myMap)) {
const value1 = myMap[prop1]
console.log('value1', value1)
}
const value2 = myMap[prop2]
/*
TS Error:
Element implicitly has an 'any' type because expression of type '"b"' can't be used to index type '{}'.
Property 'b' does not exist on type '{}'.(7053)
*/
No entanto, uma coisa é preocupante e que eu não entendo: value1 avalia para o tipo never. No entanto, não há erro de TS. O que está acontecendo aqui?
Resposta #2
Consegui fazê-lo funcionar usando também uma função de guarda de tipo, mas não retornando key is keyof MyMap. Em vez disso, eu tinha um tipo para o objeto payload de Record<string, ValidValue | undefined> e então a guarda de tipo retorna payload is Record<K, ValidValue> onde K é o tipo do parâmetro key.
Você pode brincar com isso no TS Playground.
// Type alias for convenience
type Payload = Record<string, ValidValue | undefined>;
const payload: Payload = await myApiCall()
function isValidKey<K extends string>(key: K, payload: Payload): payload is Record<K, ValidValue> {
return key in payload;
}
const prop1 = 'a'
const prop2 = 'b'
if (isValidKey(prop1, payload)) {
const value1 = payload[prop1] // the type of `value1` should evaluate to `ValidValue` ✅
}
const value2 = payload[prop2] // the type of `value2` should evaluate to `ValidValue | undefined` ✅
