Domine as verificações de propriedades em excesso do TypeScript para prevenir erros em tempo de execução e aprimorar a segurança de tipos de objetos para aplicações JavaScript robustas e previsíveis.
Verificações de Propriedades em Excesso do TypeScript: Fortalecendo a Segurança de Tipos de Seus Objetos
No universo do desenvolvimento de software moderno, especialmente com JavaScript, garantir a integridade e a previsibilidade do seu código é fundamental. Embora o JavaScript ofereça imensa flexibilidade, ele pode, por vezes, levar a erros em tempo de execução devido a estruturas de dados inesperadas ou incompatibilidades de propriedades. É aqui que o TypeScript se destaca, fornecendo capacidades de tipagem estática que detectam muitos erros comuns antes que eles se manifestem em produção. Uma das funcionalidades mais poderosas, e por vezes mal compreendida, do TypeScript é a sua verificação de propriedades em excesso.
Este post aprofunda-se nas verificações de propriedades em excesso do TypeScript, explicando o que são, por que são cruciais para a segurança de tipos de objetos e como aproveitá-las eficazmente para construir aplicações mais robustas e previsíveis. Exploraremos vários cenários, armadilhas comuns e melhores práticas para ajudar desenvolvedores de todo o mundo, independentemente da sua experiência, a dominar este mecanismo vital do TypeScript.
Entendendo o Conceito Central: O que são Verificações de Propriedades em Excesso?
Em sua essência, a verificação de propriedades em excesso do TypeScript é um mecanismo do compilador que o impede de atribuir um literal de objeto a uma variável cujo tipo não permite explicitamente essas propriedades extras. Em termos mais simples, se você definir um literal de objeto e tentar atribuí-lo a uma variável com uma definição de tipo específica (como uma interface ou um apelido de tipo), e esse literal contiver propriedades não declaradas no tipo definido, o TypeScript sinalizará isso como um erro durante a compilação.
Vamos ilustrar com um exemplo básico:
interface User {
name: string;
age: number;
}
const newUser: User = {
name: 'Alice',
age: 30,
email: 'alice@example.com' // Erro: O literal de objeto só pode especificar propriedades conhecidas, e 'email' não existe no tipo 'User'.
};
Neste trecho, definimos uma `interface` chamada `User` com duas propriedades: `name` e `age`. Quando tentamos criar um literal de objeto com uma propriedade adicional, `email`, e atribuí-lo a uma variável tipada como `User`, o TypeScript detecta imediatamente a incompatibilidade. A propriedade `email` é uma propriedade 'em excesso' porque não está definida na interface `User`. Esta verificação é realizada especificamente quando você usa um literal de objeto para atribuição.
Por que as Verificações de Propriedades em Excesso são Importantes?
A importância das verificações de propriedades em excesso reside na sua capacidade de impor um contrato entre seus dados e a estrutura esperada. Elas contribuem para a segurança de tipos de objetos de várias maneiras críticas:
- Prevenindo Erros de Digitação e Ortografia: Muitos bugs em JavaScript surgem de simples erros de digitação. Se você pretende atribuir um valor a `age`, mas acidentalmente digita `agee`, uma verificação de propriedade em excesso detectará isso como uma propriedade 'escrita incorretamente', prevenindo um potencial erro em tempo de execução onde `age` poderia ser `undefined` ou estar ausente.
- Garantindo a Adesão a Contratos de API: Ao interagir com APIs, bibliotecas ou funções que esperam objetos com formatos específicos, as verificações de propriedades em excesso garantem que você está passando dados que estão em conformidade com essas expectativas. Isso é particularmente valioso em equipes grandes e distribuídas ou ao integrar com serviços de terceiros.
- Melhorando a Legibilidade e Manutenibilidade do Código: Ao definir claramente a estrutura esperada dos objetos, essas verificações tornam seu código mais autodocumentado. Os desenvolvedores podem entender rapidamente quais propriedades um objeto deve possuir sem precisar rastrear lógicas complexas.
- Reduzindo Erros em Tempo de Execução: O benefício mais direto é a redução de erros em tempo de execução. Em vez de encontrar erros de `TypeError` ou acesso a `undefined` em produção, esses problemas são apresentados como erros em tempo de compilação, tornando-os mais fáceis e baratos de corrigir.
- Facilitando a Refatoração: Quando você refatora seu código e altera o formato de uma interface ou tipo, as verificações de propriedades em excesso destacam automaticamente onde seus literais de objeto podem não estar mais em conformidade, agilizando o processo de refatoração.
Quando as Verificações de Propriedades em Excesso se Aplicam?
É crucial entender as condições específicas sob as quais o TypeScript realiza essas verificações. Elas são aplicadas principalmente a literais de objeto quando são atribuídos a uma variável ou passados como argumento para uma função.
Cenário 1: Atribuindo Literais de Objeto a Variáveis
Como visto no exemplo `User` acima, a atribuição direta de um literal de objeto com propriedades extras a uma variável tipada aciona a verificação.
Cenário 2: Passando Literais de Objeto para Funções
Quando uma função espera um argumento de um tipo específico, e você passa um literal de objeto que contém propriedades em excesso, o TypeScript o sinalizará.
interface Product {
id: number;
name: string;
}
function displayProduct(product: Product): void {
console.log(`Product ID: ${product.id}, Name: ${product.name}`);
}
displayProduct({
id: 101,
name: 'Laptop',
price: 1200 // Erro: O argumento do tipo '{ id: number; name: string; price: number; }' não é atribuível ao parâmetro do tipo 'Product'.
// O literal de objeto só pode especificar propriedades conhecidas, e 'price' não existe no tipo 'Product'.
});
Aqui, a propriedade `price` no literal de objeto passado para `displayProduct` é uma propriedade em excesso, pois a interface `Product` não a define.
Quando as Verificações de Propriedades em Excesso *Não* se Aplicam?
Entender quando essas verificações são contornadas é igualmente importante para evitar confusão e saber quando você pode precisar de estratégias alternativas.
1. Quando Não se Utiliza Literais de Objeto para Atribuição
Se você atribuir um objeto que não é um literal de objeto (por exemplo, uma variável que já contém um objeto), a verificação de propriedade em excesso é normalmente contornada.
interface Config {
timeout: number;
}
function setupConfig(config: Config) {
console.log(`Timeout set to: ${config.timeout}`);
}
const userProvidedConfig = {
timeout: 5000,
retries: 3 // Esta propriedade 'retries' é uma propriedade em excesso de acordo com 'Config'
};
setupConfig(userProvidedConfig); // Sem erro!
// Embora userProvidedConfig tenha uma propriedade extra, a verificação é ignorada
// porque não é um literal de objeto sendo passado diretamente.
// O TypeScript verifica o tipo de userProvidedConfig em si.
// Se userProvidedConfig fosse declarado com o tipo Config, um erro ocorreria antes.
// No entanto, se declarado como 'any' ou um tipo mais amplo, o erro é adiado.
// Uma maneira mais precisa de mostrar o contorno:
let anotherConfig;
if (Math.random() > 0.5) {
anotherConfig = {
timeout: 1000,
host: 'localhost' // Propriedade em excesso
};
} else {
anotherConfig = {
timeout: 2000,
port: 8080 // Propriedade em excesso
};
}
setupConfig(anotherConfig as Config); // Sem erro devido à asserção de tipo e ao contorno
// A chave é que 'anotherConfig' não é um literal de objeto no ponto de atribuição para setupConfig.
// Se tivéssemos uma variável intermediária tipada como 'Config', a atribuição inicial falharia.
// Exemplo de variável intermediária:
let intermediateConfig: Config;
intermediateConfig = {
timeout: 3000,
logging: true // Erro: O literal de objeto só pode especificar propriedades conhecidas, e 'logging' não existe no tipo 'Config'.
};
No primeiro exemplo `setupConfig(userProvidedConfig)`, `userProvidedConfig` é uma variável que contém um objeto. O TypeScript verifica se `userProvidedConfig` como um todo está em conformidade com o tipo `Config`. Ele não aplica a verificação estrita de literal de objeto a `userProvidedConfig` em si. Se `userProvidedConfig` fosse declarado com um tipo que não correspondesse a `Config`, um erro ocorreria durante sua declaração ou atribuição. O contorno acontece porque o objeto já foi criado e atribuído a uma variável antes de ser passado para a função.
2. Asserções de Tipo
Você pode contornar as verificações de propriedades em excesso usando asserções de tipo, embora isso deva ser feito com cautela, pois anula as garantias de segurança do TypeScript.
interface Settings {
theme: 'dark' | 'light';
}
const mySettings = {
theme: 'dark',
fontSize: 14 // Propriedade em excesso
} as Settings;
// Sem erro aqui por causa da asserção de tipo.
// Estamos dizendo ao TypeScript: "Confie em mim, este objeto está em conformidade com Settings."
console.log(mySettings.theme);
// console.log(mySettings.fontSize); // Isso causaria um erro em tempo de execução se fontSize não estivesse realmente lá.
3. Usando Assinaturas de Índice ou Sintaxe de Espalhamento (Spread) em Definições de Tipo
Se sua interface ou apelido de tipo permitir explicitamente propriedades arbitrárias, as verificações de propriedades em excesso não se aplicarão.
Usando Assinaturas de Índice:
interface FlexibleObject {
id: number;
[key: string]: any; // Permite qualquer chave de string com qualquer valor
}
const flexibleItem: FlexibleObject = {
id: 1,
name: 'Widget',
version: '1.0.0'
};
// Sem erro porque 'name' e 'version' são permitidos pela assinatura de índice.
console.log(flexibleItem.name);
Usando Sintaxe de Espalhamento em Definições de Tipo (menos comum para contornar verificações diretamente, mais para definir tipos compatíveis):
Embora não seja um contorno direto, o espalhamento permite a criação de novos objetos que incorporam propriedades existentes, e a verificação se aplica ao novo literal formado.
4. Usando `Object.assign()` ou Sintaxe de Espalhamento (Spread) para Mesclar
Quando você usa `Object.assign()` ou a sintaxe de espalhamento (`...`) para mesclar objetos, a verificação de propriedade em excesso se comporta de maneira diferente. Ela se aplica ao literal de objeto resultante que está sendo formado.
interface BaseConfig {
host: string;
}
interface ExtendedConfig extends BaseConfig {
port: number;
}
const defaultConfig: BaseConfig = {
host: 'localhost'
};
const userConfig = {
port: 8080,
timeout: 5000 // Propriedade em excesso em relação a BaseConfig, mas esperada pelo tipo mesclado
};
// Espalhando em um novo literal de objeto que está em conformidade com ExtendedConfig
const finalConfig: ExtendedConfig = {
...defaultConfig,
...userConfig
};
// Isso geralmente está correto porque 'finalConfig' é declarado como 'ExtendedConfig'
// e as propriedades correspondem. A verificação é sobre o tipo de 'finalConfig'.
// Vamos considerar um cenário em que *falharia*:
interface SmallConfig {
key: string;
}
const data1 = { key: 'abc', value: 123 }; // 'value' é extra aqui
const data2 = { key: 'xyz', status: 'active' }; // 'status' é extra aqui
// Tentando atribuir a um tipo que não acomoda extras
// const combined: SmallConfig = {
// ...data1, // Erro: O literal de objeto só pode especificar propriedades conhecidas, e 'value' não existe no tipo 'SmallConfig'.
// ...data2 // Erro: O literal de objeto só pode especificar propriedades conhecidas, e 'status' não existe no tipo 'SmallConfig'.
// };
// O erro ocorre porque o literal de objeto formado pela sintaxe de espalhamento
// contém propriedades ('value', 'status') não presentes em 'SmallConfig'.
// Se criarmos uma variável intermediária com um tipo mais amplo:
const temp: any = {
...data1,
...data2
};
// Então, ao atribuir a SmallConfig, a verificação de propriedade em excesso é contornada na criação do literal inicial,
// mas a verificação de tipo na atribuição ainda pode ocorrer se o tipo de temp for inferido mais estritamente.
// No entanto, se temp for 'any', nenhuma verificação acontece até a atribuição a 'combined'.
// Vamos refinar o entendimento do espalhamento com verificações de propriedades em excesso:
// A verificação acontece quando o literal de objeto criado pela sintaxe de espalhamento é atribuído
// a uma variável ou passado para uma função que espera um tipo mais específico.
interface SpecificShape {
id: number;
}
const objA = { id: 1, extra1: 'hello' };
const objB = { id: 2, extra2: 'world' };
// Isso falhará se SpecificShape não permitir 'extra1' ou 'extra2':
// const merged: SpecificShape = {
// ...objA,
// ...objB
// };
// A razão pela qual falha é que a sintaxe de espalhamento efetivamente cria um novo literal de objeto.
// Se objA e objB tivessem chaves sobrepostas, a última prevaleceria. O compilador
// vê este literal resultante e o verifica em relação a 'SpecificShape'.
// Para fazer funcionar, você pode precisar de um passo intermediário ou um tipo mais permissivo:
const tempObj = {
...objA,
...objB
};
// Agora, se tempObj tiver propriedades que não estão em SpecificShape, a atribuição falhará:
// const mergedCorrected: SpecificShape = tempObj; // Erro: O literal de objeto só pode especificar propriedades conhecidas...
// A chave é que o compilador analisa o formato do literal de objeto que está sendo formado.
// Se esse literal contiver propriedades não definidas no tipo de destino, é um erro.
// O caso de uso típico para a sintaxe de espalhamento com verificações de propriedades em excesso:
interface UserProfile {
userId: string;
username: string;
}
interface AdminProfile extends UserProfile {
adminLevel: number;
}
const baseUserData: UserProfile = {
userId: 'user-123',
username: 'coder'
};
const adminData = {
adminLevel: 5,
lastLogin: '2023-10-27'
};
// É aqui que a verificação de propriedade em excesso é relevante:
// const adminProfile: AdminProfile = {
// ...baseUserData,
// ...adminData // Erro: O literal de objeto só pode especificar propriedades conhecidas, e 'lastLogin' não existe no tipo 'AdminProfile'.
// };
// O literal de objeto criado pelo espalhamento tem 'lastLogin', que não está em 'AdminProfile'.
// Para corrigir isso, 'adminData' idealmente deveria estar em conformidade com AdminProfile ou a propriedade em excesso deveria ser tratada.
// Abordagem corrigida:
const validAdminData = {
adminLevel: 5
};
const adminProfileCorrect: AdminProfile = {
...baseUserData,
...validAdminData
};
console.log(adminProfileCorrect.userId);
console.log(adminProfileCorrect.adminLevel);
A verificação de propriedade em excesso se aplica ao literal de objeto resultante criado pela sintaxe de espalhamento. Se este literal resultante contiver propriedades não declaradas no tipo de destino, o TypeScript relatará um erro.
Estratégias para Lidar com Propriedades em Excesso
Embora as verificações de propriedades em excesso sejam benéficas, existem cenários legítimos onde você pode ter propriedades extras que deseja incluir ou processar de forma diferente. Aqui estão as estratégias comuns:
1. Propriedades Rest com Apelidos de Tipo ou Interfaces
Você pode usar a sintaxe de parâmetro rest (`...rest`) dentro de apelidos de tipo ou interfaces para capturar quaisquer propriedades restantes que não são explicitamente definidas. Esta é uma maneira limpa de reconhecer e coletar essas propriedades em excesso.
interface UserProfile {
id: number;
name: string;
}
interface UserWithMetadata extends UserProfile {
metadata: {
[key: string]: any;
};
}
// Ou, mais comumente, com um apelido de tipo e sintaxe rest:
type UserProfileWithMetadata = UserProfile & {
[key: string]: any;
};
const user1: UserProfileWithMetadata = {
id: 1,
name: 'Bob',
email: 'bob@example.com',
isAdmin: true
};
// Sem erro, pois 'email' e 'isAdmin' são capturados pela assinatura de índice em UserProfileWithMetadata.
console.log(user1.email);
console.log(user1.isAdmin);
// Outra maneira usando parâmetros rest em uma definição de tipo:
interface ConfigWithRest {
apiUrl: string;
timeout?: number;
// Captura todas as outras propriedades em 'extraConfig'
[key: string]: any;
}
const appConfig: ConfigWithRest = {
apiUrl: 'https://api.example.com',
timeout: 5000,
featureFlags: {
newUI: true,
betaFeatures: false
}
};
console.log(appConfig.featureFlags);
Usar `[key: string]: any;` ou assinaturas de índice semelhantes é a maneira idiomática de lidar com propriedades adicionais arbitrárias.
2. Desestruturação com Sintaxe Rest
Quando você recebe um objeto e precisa extrair propriedades específicas enquanto mantém o resto, a desestruturação com a sintaxe rest é inestimável.
interface Employee {
employeeId: string;
department: string;
}
function processEmployeeData(data: Employee & { [key: string]: any }) {
const { employeeId, department, ...otherDetails } = data;
console.log(`Employee ID: ${employeeId}`);
console.log(`Department: ${department}`);
console.log('Other details:', otherDetails);
// otherDetails conterá quaisquer propriedades não desestruturadas explicitamente,
// como 'salary', 'startDate', etc.
}
const employeeInfo = {
employeeId: 'emp-789',
department: 'Engineering',
salary: 90000,
startDate: '2022-01-15'
};
processEmployeeData(employeeInfo);
// Mesmo que employeeInfo tivesse uma propriedade extra inicialmente, a verificação de propriedade em excesso
// é contornada se a assinatura da função a aceitar (por exemplo, usando uma assinatura de índice).
// Se processEmployeeData fosse tipado estritamente como 'Employee', e employeeInfo tivesse 'salary',
// um erro ocorreria SE employeeInfo fosse um literal de objeto passado diretamente.
// Mas aqui, employeeInfo é uma variável, e o tipo da função lida com os extras.
3. Definindo Explicitamente Todas as Propriedades (se conhecidas)
Se você conhece as potenciais propriedades adicionais, a melhor abordagem é adicioná-las à sua interface ou apelido de tipo. Isso fornece a maior segurança de tipo.
interface UserProfile {
id: number;
name: string;
email?: string; // Email opcional
}
const userWithEmail: UserProfile = {
id: 2,
name: 'Charlie',
email: 'charlie@example.com'
};
const userWithoutEmail: UserProfile = {
id: 3,
name: 'David'
};
// Se tentarmos adicionar uma propriedade que não está em UserProfile:
// const userWithExtra: UserProfile = {
// id: 4,
// name: 'Eve',
// phoneNumber: '555-1234'
// }; // Erro: O literal de objeto só pode especificar propriedades conhecidas, e 'phoneNumber' não existe no tipo 'UserProfile'.
4. Usando `as` para Asserções de Tipo (com cautela)
Como mostrado anteriormente, as asserções de tipo podem suprimir as verificações de propriedades em excesso. Use isso com moderação e apenas quando tiver certeza absoluta sobre o formato do objeto.
interface ProductConfig {
id: string;
version: string;
}
// Imagine que isso vem de uma fonte externa ou de um módulo menos estrito
const externalConfig = {
id: 'prod-abc',
version: '1.2',
debugMode: true // Propriedade em excesso
};
// Se você sabe que 'externalConfig' sempre terá 'id' e 'version' e quer tratá-lo como ProductConfig:
const productConfig = externalConfig as ProductConfig;
// Esta asserção contorna a verificação de propriedade em excesso em `externalConfig` em si.
// No entanto, se você passasse um literal de objeto diretamente:
// const productConfigLiteral: ProductConfig = {
// id: 'prod-xyz',
// version: '2.0',
// debugMode: false
// }; // Erro: O literal de objeto só pode especificar propriedades conhecidas, e 'debugMode' não existe no tipo 'ProductConfig'.
5. Guardas de Tipo (Type Guards)
Para cenários mais complexos, as guardas de tipo podem ajudar a refinar os tipos e lidar condicionalmente com as propriedades.
interface Shape {
kind: 'circle' | 'square';
}
interface Circle extends Shape {
kind: 'circle';
radius: number;
}
interface Square extends Shape {
kind: 'square';
sideLength: number;
}
function calculateArea(shape: Shape) {
if (shape.kind === 'circle') {
// O TypeScript sabe que 'shape' é um Circle aqui
console.log(Math.PI * shape.radius ** 2);
} else if (shape.kind === 'square') {
// O TypeScript sabe que 'shape' é um Square aqui
console.log(shape.sideLength ** 2);
}
}
const circleData = {
kind: 'circle' as const, // Usando 'as const' para inferência de tipo literal
radius: 10,
color: 'red' // Propriedade em excesso
};
// Quando passado para calculateArea, a assinatura da função espera 'Shape'.
// A função em si acessará 'kind' corretamente.
// Se calculateArea estivesse esperando 'Circle' diretamente e recebesse circleData
// como um literal de objeto, 'color' seria um problema.
// Vamos ilustrar a verificação de propriedade em excesso com uma função que espera um subtipo específico:
function processCircle(circle: Circle) {
console.log(`Processing circle with radius: ${circle.radius}`);
}
// processCircle(circleData); // Erro: O argumento do tipo '{ kind: "circle"; radius: number; color: string; }' não é atribuível ao parâmetro do tipo 'Circle'.
// O literal de objeto só pode especificar propriedades conhecidas, e 'color' não existe no tipo 'Circle'.
// Para corrigir isso, você pode desestruturar ou usar um tipo mais permissivo para circleData:
const { color, ...circleDataWithoutColor } = circleData;
processCircle(circleDataWithoutColor);
// Ou defina circleData para incluir um tipo mais amplo:
const circleDataWithExtras: Circle & { [key: string]: any } = {
kind: 'circle',
radius: 15,
color: 'blue'
};
processCircle(circleDataWithExtras); // Agora funciona.
Armadilhas Comuns e Como Evitá-las
Até mesmo desenvolvedores experientes podem ser pegos de surpresa pelas verificações de propriedades em excesso. Aqui estão algumas armadilhas comuns:
- Confundir Literais de Objeto com Variáveis: O erro mais frequente é não perceber que a verificação é específica para literais de objeto. Se você atribuir a uma variável primeiro e depois passar essa variável, a verificação é frequentemente contornada. Lembre-se sempre do contexto da atribuição.
- Uso Excessivo de Asserções de Tipo (`as`): Embora úteis, o uso excessivo de asserções de tipo nega os benefícios do TypeScript. Se você se pega usando `as` frequentemente para contornar verificações, isso pode indicar que seus tipos ou a forma como você está construindo objetos precisam de refinamento.
- Não Definir Todas as Propriedades Esperadas: Se você está trabalhando com bibliotecas ou APIs que retornam objetos com muitas propriedades potenciais, certifique-se de que seus tipos capturem as que você precisa e use assinaturas de índice ou propriedades rest para o restante.
- Aplicar Incorretamente a Sintaxe de Espalhamento: Entenda que o espalhamento cria um novo literal de objeto. Se este novo literal contiver propriedades em excesso em relação ao tipo de destino, a verificação será aplicada.
Considerações Globais e Melhores Práticas
Ao trabalhar em um ambiente de desenvolvimento global e diversificado, aderir a práticas consistentes em torno da segurança de tipos é crucial:
- Padronize as Definições de Tipo: Garanta que sua equipe tenha um entendimento claro de como definir interfaces e apelidos de tipo, especialmente ao lidar com dados externos ou estruturas de objetos complexas.
- Documente as Convenções: Documente as convenções da sua equipe para lidar com propriedades em excesso, seja através de assinaturas de índice, propriedades rest ou funções utilitárias específicas.
- Eduque Novos Membros da Equipe: Certifique-se de que os desenvolvedores novos no TypeScript ou no seu projeto entendam o conceito e a importância das verificações de propriedades em excesso.
- Priorize a Legibilidade: Busque por tipos que sejam o mais explícitos possível. Se um objeto deve ter um conjunto fixo de propriedades, defina-as explicitamente em vez de depender de assinaturas de índice, a menos que a natureza dos dados realmente o exija.
- Use Linters e Formatadores: Ferramentas como o ESLint com o plugin TypeScript ESLint podem ser configuradas para impor padrões de codificação e detectar possíveis problemas relacionados aos formatos dos objetos.
Conclusão
As verificações de propriedades em excesso do TypeScript são um pilar da sua capacidade de fornecer uma segurança de tipos de objeto robusta. Ao entender quando e por que essas verificações ocorrem, os desenvolvedores podem escrever um código mais previsível e menos propenso a erros.
Para desenvolvedores ao redor do mundo, abraçar esta funcionalidade significa menos surpresas em tempo de execução, colaboração mais fácil e bases de código mais fáceis de manter. Seja construindo um pequeno utilitário ou uma aplicação empresarial de grande escala, dominar as verificações de propriedades em excesso sem dúvida elevará a qualidade e a confiabilidade dos seus projetos JavaScript.
Pontos-chave:
- As verificações de propriedades em excesso se aplicam a literais de objeto atribuídos a variáveis ou passados para funções com tipos específicos.
- Elas detectam erros de digitação, impõem contratos de API e reduzem erros em tempo de execução.
- As verificações são contornadas para atribuições não literais, asserções de tipo e tipos com assinaturas de índice.
- Use propriedades rest (`[key: string]: any;`) ou desestruturação para lidar com propriedades em excesso legítimas de forma elegante.
- A aplicação e o entendimento consistentes dessas verificações promovem uma segurança de tipos mais forte em equipes de desenvolvimento globais.
Ao aplicar conscientemente esses princípios, você pode aprimorar significativamente a segurança e a manutenibilidade do seu código TypeScript, levando a resultados de desenvolvimento de software mais bem-sucedidos.