Tipo avançado no TypeScript
Recentemente criei um tipo relativamente complexo no TypeScript e vou explicar ele aqui. Não consegui encontrar qual é o nome oficial dele. Se você souber, não deixe de comentar!
Como tudo que exige um pouco mais de raciocínio quando faço, vou quebrar a explicação em partes. Os exemplos utilizados aqui não são os mesmos que usei quando desenvolvi a lógica pela primeira vez, mas com eles fica mais simples para entender.
Primeiro criei o tipo Person, com as propriedades name, age e address.
type Person = {
name: string
age: number
address: {
street: string
number: number
}
}Depois defini o que eu preciso. Variáveis do meu tipo devem guardar o valor de alguma propriedade do tipo Person. A propriedade que ele guarda deve ser definida em property, e o valor em value. Chamei esse tipo de PersonPropertyValue.
Por exemplo:
// valid
const object: PersonPropertyValue = {
propertyValue: {
property: 'age',
value: 2
}
}ou
// valid
const object: PersonPropertyValue = {
propertyValue: {
property: 'address',
value: {
street: 'Saint Street',
number: 3
}
}
}devem ser válidos. A constante:
// invalid
const object: PersonPropertyValue = {
propertyValue: {
property: 'address',
value: 'Saint Street'
}
}deve ser inválida, pois address tem o valor de um objeto, e não de uma string, e o typescript deve considerar um erro em tempo de compilação.
Como fazer isso de maneira que property e value sejam dinâmicas e utilizem o tipo Person como referência?
type PersonPropertyValue = {
propertyValue: {
[K in keyof Person]: {
property: K
value: Person[K]
}
}[keyof Person]
}Funciona! Mas como funciona? O trecho [K in keyof Person] faz um loop em todas as propriedades de person, e no nosso caso, para cada propriedade, ele cria um objeto seguindo o que foi declarado na sequência. Aqui aconteceu o seguinte:
propertyValue: {
name: {
property: "name";
value: string;
};
age: {
property: "age";
value: number;
};
address: {
property: "address";
value: {
street: string;
number: number;
};
};
}Mas não é isso que precisamos. Queremos que propertyValue “entre” em cada uma de suas propriedades. Então utilizamos o [keyof Person]. Com ele, cada propriedade de propertyValue é acessada e acaba sendo removida do nosso tipo, mas seus valores continuam aqui. Acabamos com o seguinte resultado:
type PersonPropertyValue = {
propertyValue: {
property: "name";
value: string;
} |
{
property: "age";
value: number;
} |
{
property: "address";
value: {
street: string;
number: number;
};
};
}Que é exatamente o que precisamos. E o melhor, isso está utilizando o tipo Person como referência. Qualquer alteração nele será refletido aqui.