Qualquer desenvolvedor frontend conhece a frustração de ver a infame mensagem "não é uma função" aparecer. Ela lembra que, mesmo que seu código alcance seu objetivo, você não conseguirá garantir sua consistência ou confiabilidade. Sem uma digitação rigorosa, não há como evitar pequenos deslizes por falta de café – ou sono, dependendo da sua dedicação.
Além da sua própria humanidade, há a desajeitação de outros desenvolvedores. Bibliotecas de terceiros aumentaram a diversão ao introduzir mudanças quebradas. Onde e como essa variável é passada? O que dá certo acidentalmente e o que misteriosamente dá errado? Repetidamente lancei meu código nas profundezas do inferno e tentei reescrever, tudo para experimentar a mesma frustração novamente.
Com o TypeScript, deixamos esses terrores para trás ao introduzir — como o nome sugere — tipos. Anotamos as variáveis e funções com o tipo de valor que elas carregam, esperam ou retornam e transformamos JavaScript em uma linguagem estritamente tipada. A cada linha de código que escrevemos, testamos automaticamente a confiabilidade e consistência do nosso código. Vamos dar uma olhada em um exemplo básico.

Além disso, podemos identificar inconsistências facilmente, pois o compilador grita como um assassinato quando encontrar uma. Quanto ao uso dessas encantadoras bibliotecas de terceiros, o TypeScript oferece aos desenvolvedores uma forma de se comunicar e informar sobre seu código sem afogar outros desenvolvedores em um mar de documentação.
Acredito que esse seja o uso central do TypeScript. No entanto, também acredito que só arranhamos a superfície de algo muito mais poderoso. Deixe-me te guiar por alguns casos interessantes.
Saber ou não saber, desconhecido vs. qualquer
Imagine que estamos escrevendo uma função que precisa devolver a entrada como está. Podemos usar o tipo 'qualquer' para fazer isso. O tipo 'qualquer' aceita tudo; strings e números; arrays e objetos; cartões de crédito e pedidos de desculpas. Bem, talvez não o último, mas aceita qualquer tipo de variável.

Tudo isso é aceito porque nossa variável tem o tipo 'qualquer'.
No entanto, há algo estranho acontecendo na última linha. Podemos atribuir descaradamente um 'número' a uma variável que originalmente foi declarada como um tipo 'string'. Não é exatamente isso que estou tentando provar aqui, né? Não se preocupe, lá vem o tipo 'desconhecido' para resgate!

E estamos de volta ao negócio! Podemos atribuir qualquer coisa ao tipo 'desconhecido', mas não podemos atribuir a variáveis "mais fortes" com tipos.
Curado com genéricos
Acabamos de ver como podemos usar os tipos 'qualquer' e 'desconhecido' para criar uma função que aceite qualquer coisa. No entanto, precisamos confiar que não trocamos a entrada por algum outro tipo de valor sem saber. Afinal, nossa função devolverá um tipo 'qualquer'.
Para resolver essa insegurança, podemos introduzir um tipo genérico. Um tipo genérico nos permite garantir que retornamos o tipo que foi passado para nossa função.

Quando chamamos a função, podemos especificar o tipo da nossa entrada fornecendo qual é o tipo 'T'. A função especifica que devemos devolver o mesmo tipo que 'T', assim sabemos o que recebemos. Relaxe e aproveite um Scooby Snack – talvez depois de ler o artigo.
Quanto à notação de funções, repare que não uso a notação de seta ali. Se quisermos usar a notação de seta, pode ficar um pouco confusa ao usar JSX.

Você pode ver que a notação que esperamos que funcione não funcionará ao usar JSX. A razão para isso é que o compilador confunde a definição do tipo de entrada para a sintaxe JSX. Isso pode ser contornado adicionando uma vírgula e omitindo o segundo tipo. Essa notação é inválida para JSX e faz o compilador interpretar nosso código como uma definição genérica de tipo. Ufa!
Discussão sobre discussões, com o tipo nunca
Dê uma volta pela memória. Já precisou escrever uma função que deveria aceitar dois tipos de valores, mas que só deveria receber um de cada vez? Você se lembra de ter escrito algo como o seguinte?
_"Se ambos os valores forem fornecidos, jogue um erro porque só devemos fornecer um valor ou outro." _

E talvez mais comumente.
"Se esse argumento for fornecido, vá com este. Se não for fornecido, siga com aquele"

E Deus me livre de esquecer a regra mais importante.
_"Se nenhum valor for fornecido, lance um erro." _

É uma prática prolixa – e feia como o pecado. Felizmente, no TypeScript existe uma forma mais natural e menos intrusiva de atender a esse requisito. Que 'nunca' tente!

Queremos que a função aceite um objeto com propriedades de qualquer um dos tipos, mas não de ambos. Declaramos o argumento que não queremos como um tipo 'nunca', então o compilador gerará um erro quando tentarmos passar nenhuma ou todas as propriedades.
What é a diferença entre declarar uma propriedade como um tipo 'nunca' ou simplesmente removê-la do tipo?
Se simplesmente deixássemos de fora a propriedade que não queremos, o compilador aceitará chamar a função com ambos os argumentos. Isso novamente passaria a responsabilidade de lidar com esse caso para a função. Faça o que a microgestão faz de melhor; passe a responsabilidade!
Tipos de união, sobrepostos e Excluir
Se quisermos permitir múltiplos valores, podemos usar '|' para criar um tipo de união.

Fácil o suficiente, então vamos para algo mais interessante.
Em vez de combinar tipos, podemos usar '&' para permitir apenas tipos sobrepostos. Isso pode ser uma prática útil ao escrever uma função que chamará duas outras funções que aceitam algumas, mas não todas, dos mesmos tipos. Podemos garantir que apenas tipos que serão aceitos por ambas as funções podem ser fornecidos.

Ótimo, selecionamos com sucesso todos e os tipos sobrepostos! No entanto, resta um caso em que os tipos únicos são selecionados. O tipo 'Excluir' pode ser usado para isso, pois excluirá quaisquer valores que estejam no primeiro argumento e que também estejam no segundo.

Estamos na metade do caminho. Selecionamos o primeiro tipo não sobreposto, mas o segundo tipo não sobreposto é rejeitado. Vamos tentar corrigir isso combinando as potências de '|' e 'Excluir'.

E agora terminamos, "PALMAS PALMAS" para nós!
E agora, com objetos, aqui estão Pick e Omit
Combinar tipos de objetos é bem simples. Na seção anterior, usamos '&' para selecionar os tipos sobrepostos. Com objetos, porém, '& ' resulta em um tipo que exige as propriedades de ambos os tipos.

É um pouco mais complexo subtrair tipos. Podemos usar o tipo 'Omit' para remover propriedades exigidas de um objeto. No entanto, o segundo parâmetro 'Omit' não aceita um objeto, mas – assim como o tipo 'Exlude' – um tipo literal de string. Podemos obter as chaves de um objeto usando 'keyof', resultando na seguinte solução.

Se quisermos manter apenas as propriedades sobrepostas, podemos usar 'keyof' novamente, mas desta vez com o tipo 'Pick'.

Acompanhe, com Registro
Se você é como eu e verifica seus bolsos a cada 5 minutos para ver se perdeu algo, o TypeScript é um grande alívio. Com o TypeScript você pode garantir que não esqueceu de declarar uma chave com o tipo 'Registro'.
Vamos declarar duas das "quatro grandes" bandas de metal com algumas informações adicionais. (Desculpe, Metallica e Anthrax.)

Nossa constante exigirá que ambas as bandas sejam declaradas para aprovação.
Solte, com Partial
Agora gire esse 'Registro' na direta. E se não precisarmos ter todas as teclas? Usando nossos tipos previamente definidos, desta vez junto com 'Parcial'.

Agora podemos definir um subconjunto das bandas.
Are não estamos nos prejudicando por sermos tão relaxados?
Não! Agora permitimos explicitamente certa flexibilidade em vez de permitir tudo implicitamente. E fique tranquilo, ainda precisamos definir todas as propriedades dos outros tipos!
Imponha o limite, com Obrigatório
Por outro lado, podemos usar o tipo 'Obrigatório' para tornar as propriedades opcionais obrigatórias.

Proteja sua propriedade, com Readonly
Podemos declarar variáveis como constantes para garantir que seu valor não seja alterado. No entanto, quando declaramos um objeto como constante, ainda podemos alterar os valores de suas propriedades. Podemos usar o tipo 'Somente Leitura' para evitar isso.

Simplesmente embrulhamos nosso texto no texto 'Somente Leitura' e é basicamente isso!
Conclusão: corda;
Claro, existem muitos outros casos de uso e muitos recursos interessantes. Mas, com esses exemplos, vimos que o TypeScript nos obriga a ser rigorosos, ao mesmo tempo em que nos oferece muita flexibilidade. Quaisquer que sejam os tipos com os quais você está trabalhando, sempre há uma maneira de conseguir o que você quer. Enquanto isso, isso torna nosso código confiável e prepara qualquer projeto para crescer. Com o suporte estabelecido ao TypeScript e os muitos projetos que o utilizam, podemos dizer que o TypeScript não vai desaparecer tão cedo.
Aproveite e explore o poder dos tipos Typescript!