Considere uma função auxiliar
const isKeyOf = <K extends PropertyKey, O extends object>(key: K, object: O): key is K & keyof O => key in object
Aqui, isKeyOf
limita o tipo de chave ao verificar se existe uma chave em um objeto
const obj = {
a: 1,
b: 2,
c: 3
}
const key: string = "a" // type of key is string
if (isKeyOf(key, obj)) {
// type of key is "a" | "b" | "c"
}
Isso funciona bem, desde que obj não seja uma união de tipos, no entanto:
const holder: Record<number, { a: number } | { b: number }> = {
1: { a: 1 },
2: { b: 2 }
}
// type of obj is { a: number } | { b: number }
const obj = holder[1 as number]
const key: string = "a" // type of key is string
if (isKeyOf(key, obj)) {
// type of key is never!
// However, this branch does execute, and
console.log(typeof key) // prints string
console.log(obj) // prints { a: 1 }
}
Depois de alguma investigação, descobri que isso ocorre porque o keyof
operador lista apenas as chaves que são comuns a todos os tipos em uma união
type objType = { a: number } | { b: number }
type key = keyof objType // = never
ainda assim, neste caso, verifiquei se key
realmente está in objType
no tipo de guarda. Como faço para escrever um tipo de proteção que verifique isso corretamente e atribua o tipo correto à chave?
Como você mencionou, o
keyof
operador retornará apenas as chaves comuns, portanto, o seguinte tipo resultará emnever
:Para corrigi-lo, precisamos verificar
keyof
cada membro do sindicato separadamente. Portanto, nosso resultado final deve ficar assim:Podemos conseguir isso usando tipos condicionais distributivos . Os tipos de união são distribuídos sempre que são verificados em relação a alguma condição usando
extends
. Exemplo:Para garantir que não perderemos nenhum membro como no exemplo anterior, precisamos ter uma condição que seja sempre verdadeira. As condições possíveis são verificar contra
any
ou oT
próprio, desde queT extends T
seja sempre verdadeiro:Parece bom! Vamos adaptá-lo ao seu tipo de guarda :
Teste:
Parque infantil