O autor do artigo que estamos traduzindo hoje diz que o TypeScript é incrível. Quando ele começou a usar o TS, ele realmente gostou da liberdade inerente a esse idioma. Quanto mais esforço um programador fizer em seu trabalho com mecanismos específicos de TS, maiores serão os benefícios que ele receberá. Depois, ele usava anotações de tipo apenas periodicamente. Às vezes, ele usava as oportunidades para conclusão de código e dicas do compilador, mas confiava principalmente apenas em sua própria visão das tarefas que resolveu.
Com o tempo, o autor deste material percebeu que toda vez que ignora os erros detectados no estágio de compilação, coloca uma bomba-relógio em seu código que pode explodir durante a execução do programa. Cada vez que ele "lutava" com erros usando uma construção simples
as any
, tinha que pagar por isso com muitas horas de depuração.

Como resultado, ele chegou à conclusão de que é melhor não fazer isso. Ele fez amizade com o compilador, começou a prestar atenção em suas dicas. O compilador encontra problemas no código e os relata muito antes que eles possam causar danos reais. O autor do artigo, considerando-se um desenvolvedor, percebeu que o compilador é seu melhor amigo, pois o protege de si mesmo. Como não lembrar as palavras de Albus Dumbledore: "É preciso muita coragem para se manifestar contra seus inimigos, mas não menos do que isso é necessário para se manifestar contra seus amigos."
Não importa o quão bom o compilador possa ser, nem sempre é fácil agradar. Às vezes, evitar o uso de
any
tipo é muito difícil. E, às vezes, parece que
any
é a única solução razoável para algum problema.
Este material se concentra em duas situações. Ao evitar o uso de
any
tipo, é possível garantir a segurança do código, abrir as possibilidades de reutilização e torná-lo intuitivo.
Genéricos
Suponha que estamos trabalhando no banco de dados de uma escola.
getBy
função auxiliar muito conveniente,
getBy
. Para obter o objeto que representa o aluno pelo nome, podemos usar um comando no formato
getBy(model, "name", "Harry")
. Vamos dar uma olhada na implementação desse mecanismo (aqui, para não complicar o código, o banco de dados é representado por uma matriz comum).
type Student = { name: string; age: number; hasScar: boolean; }; const students: Student[] = [ { name: "Harry", age: 17, hasScar: true }, { name: "Ron", age: 17, hasScar: false }, { name: "Hermione", age: 16, hasScar: false } ]; function getBy(model, prop, value) { return model.filter(item => item[prop] === value)[0] }
Como você pode ver, temos uma boa função, mas ela não usa anotações de tipo e sua ausência também significa que essa função não pode ser chamada de segurança de tipo. Corrija.
function getBy(model: Student[], prop: string, value): Student | null { return model.filter(item => item[prop] === value)[0] || null } const result = getBy(students, "name", "Hermione") // result: Student
Portanto, nossa função já parece muito melhor. O compilador agora sabe o tipo de resultado esperado, isso será útil mais tarde. No entanto, para obter um trabalho seguro com os tipos, sacrificamos as possibilidades de reutilizar a função. E se precisarmos usá-lo para obter outras entidades? Não é possível que essa função não possa ser melhorada de forma alguma. E é mesmo.
No TypeScript, como em outras linguagens fortemente tipadas, podemos usar genéricos, também chamados de "tipos genéricos", "tipos universais", "generalizações".
Um genérico é semelhante a uma variável regular, mas, em vez de algum valor, contém uma definição de tipo. Reescrevemos o código de nossa função para que, em vez do tipo
Student
ele usasse o tipo universal
T
function getBy<T>(model: T[], prop: string, value): T | null { return model.filter(item => item[prop] === value)[0] } const result = getBy<Student>(students, "name", "Hermione") // result: Student
Beleza! Agora, a função é ideal para reutilização, enquanto a segurança de tipo ainda está do nosso lado. Observe como o tipo de
Student
é definido explicitamente na última linha do snippet de código acima, onde o
T
genérico
T
. Isso é feito para tornar o exemplo o mais claro possível, mas o compilador, de fato, pode derivar independentemente o tipo necessário; portanto, nos exemplos a seguir, não faremos esses refinamentos de tipo.
Portanto, agora temos uma função auxiliar confiável, adequada para reutilização. No entanto, ainda pode ser melhorado. E se um erro for cometido ao inserir o segundo parâmetro e, em vez de
"name"
, aparecer
"naem"
? A função se comportará como se o aluno que você está procurando simplesmente não estiver no banco de dados e, o mais desagradável, não produzirá nenhum erro. Isso pode resultar em depuração de longo prazo.
Para proteger contra esses erros, introduzimos outro tipo universal,
P
Além disso, é necessário que
P
seja uma chave do tipo
T
, portanto, se o
Student
usado aqui, é necessário que
P
seja a string
"name"
,
"age"
ou
"hasScar"
. Aqui está como fazê-lo.
function getBy<T, P extends keyof T>(model: T[], prop: P, value): T | null { return model.filter(item => item[prop] === value)[0] || null } const result = getBy(students, "naem", "Hermione") // Error: Argument of type '"naem"' is not assignable to parameter of type '"name" | "age" | "hasScar"'.
Usar genéricos e a
keyof
-
keyof
é um truque muito poderoso. Se você escreve programas em um IDE compatível com TypeScript, inserindo argumentos, pode tirar proveito dos recursos de preenchimento automático, o que é muito conveniente.
No entanto, ainda não terminamos o trabalho na função
getBy
. Ela tem um terceiro argumento, do tipo que ainda não definimos. Isso não nos convém. Até agora, não podíamos saber de antemão que tipo deveria ser, pois depende do que passamos como segundo argumento. Mas agora, como temos o tipo
P
, podemos inferir dinamicamente o tipo para o terceiro argumento. O tipo do terceiro argumento será finalmente
T[P]
. Como resultado, se
T
é
Student
e
P
é
"age"
, então
T[P]
será do tipo
number
.
function getBy<T, P extends keyof T>(model: T[], prop: P, value: T[P]): T | null { return model.filter(item => item[prop] === value)[0] || null } const result = getBy(students, "age", "17") // Error: Argument of type '"17"' is not assignable to parameter of type 'number'. const anotherResult = getBy(students, "hasScar", "true") // Error: Argument of type '"true"' is not assignable to parameter of type 'boolean'. const yetAnotherResult = getBy(students, "name", "Harry") //
Espero que agora você tenha um entendimento absolutamente claro de como usar genéricos no TypeScript, mas se quiser experimentar muito bem tudo o que deseja experimentar com o código discutido aqui, pode dar uma olhada
aqui .
Estendendo tipos existentes
Às vezes, podemos encontrar a necessidade de adicionar dados ou funcionalidade a interfaces cujo código não podemos alterar. Pode ser necessário alterar o objeto padrão, por exemplo - adicionar alguma propriedade ao objeto da
window
ou estender o comportamento de alguma biblioteca externa como o
Express
. E nos dois casos, você não tem a capacidade de afetar diretamente o objeto com o qual deseja trabalhar.
Vamos procurar uma solução para esse problema adicionando a função
getBy
você já conhece ao protótipo
Array
. Isso nos permitirá, usando essa função, construir construções sintáticas mais precisas. No momento, não estamos falando sobre se é bom ou ruim expandir objetos padrão, uma vez que nosso principal objetivo é estudar a abordagem em consideração.
Se tentarmos adicionar uma função ao protótipo
Array
, o compilador não gostará muito disso:
Array.prototype.getBy = function <T, P extends keyof T>( this: T[], prop: P, value: T[P] ): T | null { return this.filter(item => item[prop] === value)[0] || null; }; // Error: Property 'getBy' does not exist on type 'any[]'. const bestie = students.getBy("name", "Ron"); // Error: Property 'getBy' does not exist on type 'Student[]'. const potionsTeacher = (teachers as any).getBy("subject", "Potions") // ... ?
Se tentarmos tranquilizar o compilador periodicamente usando o construto
as any
, anularemos tudo o que alcançamos. O compilador será silencioso, mas você pode esquecer o trabalho seguro com os tipos.
Seria melhor estender o tipo
Array
, mas antes de fazer isso, vamos falar sobre como o TypeScript lida com situações em que duas interfaces do mesmo tipo estão presentes no código. Aqui, um esquema simples de ação é aplicado. Se possível, os anúncios serão combinados. Se você não puder combiná-los, o sistema apresentará um erro.
Portanto, esse código funciona:
interface Wand { length: number } interface Wand { core: string } const myWand: Wand = { length: 11, core: "phoenix feather" }
E este não é:
interface Wand { length: number } interface Wand { length: string }
Agora, tendo resolvido isso, vemos que nos deparamos com uma tarefa bastante simples. Ou seja, tudo o que precisamos fazer é declarar a interface
Array<T>
e adicionar a função
getBy
a ela.
interface Array<T> { getBy<P extends keyof T>(prop: P, value: T[P]): T | null; } Array.prototype.getBy = function <T, P extends keyof T>( this: T[], prop: P, value: T[P] ): T | null { return this.filter(item => item[prop] === value)[0] || null; }; const bestie = students.getBy("name", "Ron"); // ! const potionsTeacher = (teachers as any).getBy("subject", "Potions") //
Observe que a maior parte do código que você provavelmente escreverá nos arquivos do módulo, portanto, para fazer alterações na interface
Array
, você precisará acessar o escopo global. Você pode fazer isso colocando a definição de tipo dentro de
declare global
. Por exemplo, assim:
declare global { interface Array<T> { getBy<P extends keyof T>(prop: P, value: T[P]): T | null; } }
Se você deseja expandir a interface de uma biblioteca externa, provavelmente precisará acessar o
namespace
para
namespace
dessa biblioteca. Aqui está um exemplo de como adicionar o campo
userId
a
Request
na biblioteca
Express
:
declare global { namespace Express { interface Request { userId: string; } } }
Você pode experimentar o código nesta seção
aqui .
Sumário
Neste artigo, vimos técnicas para usar extensões genéricas e de tipo no TypeScript. Esperamos que o que você aprendeu hoje o ajude a escrever um código confiável, compreensível e com segurança de tipo.
Caros leitores! Como você se sente sobre qualquer tipo no TypeScript?
