
Estrutura do livro
O livro é uma coleção de pequenos ensaios (regras). As regras estão agrupadas em seções temáticas (capítulos), que podem ser acessadas de forma autônoma, dependendo da questão de interesse.
Cada cabeçalho de regra contém uma dica, portanto, verifique o índice. Se, por exemplo, você está escrevendo documentação e tem dúvidas sobre se deve escrever informações de tipo, consulte o índice e a regra 30 (“Não repita informações de tipo na documentação”).
Quase todas as conclusões do livro são demonstradas com exemplos de código. Acho que você, como eu, tende a ler livros técnicos olhando exemplos e apenas passando pela parte do texto. Claro, espero que você leia as explicações com atenção, mas cobri os pontos principais nos exemplos.
Depois de ler cada dica, você pode entender exatamente como e por que isso o ajudará a usar o TypeScript com mais eficácia. Você também entenderá se ele se tornar inutilizável de alguma forma. Lembro-me de um exemplo dado por Scott Myers, autor de Effective C ++: os desenvolvedores de software de mísseis podem ter negligenciado conselhos sobre como evitar vazamentos de recursos porque seus programas foram destruídos quando o míssil atingiu o alvo. Não estou ciente da existência de foguetes com um sistema de controle escrito em JavaScript, mas esse software está no telescópio James Webb. Por isso tem cuidado.
Cada regra termina com um bloco "Deve ser lembrado". Olhando rapidamente para ele, você pode ter uma ideia geral do material e destacar o principal. Mas eu recomendo fortemente a leitura de toda a regra.
Um trecho. REGRA 4. Acostume-se com tipagem estrutural
JavaScript é digitado de forma não intencional: se você passar um valor com as propriedades corretas para uma função, não importa como você obteve esse valor. Ela apenas usa. O TypeScript modela esse comportamento, que às vezes leva a resultados inesperados, pois o entendimento do validador sobre os tipos pode ser mais amplo do que o seu. O desenvolvimento da habilidade de digitação estruturada permitirá que você sinta melhor onde realmente há erros e escreva um código mais confiável.
Por exemplo, você está trabalhando com uma biblioteca de física e tem um tipo de vetor 2D:
interface Vector2D {
x: number;
y: number;
}
Você escreve uma função para calcular seu comprimento:
function calculateLength(v: Vector2D) {
return Math.sqrt(v.x * v.x + v.y * v.y);
}
e insira a definição do vetor nomeado:
interface NamedVector {
name: string;
x: number;
y: number;
}
A função solveLength funcionará com o NamedVector, pois contém as propriedades xey, que são number. O TypeScript entende isso:
const v: NamedVector = { x: 3, y: 4, name: 'Zee' };
calculateLength(v); // ok, 5.
O interessante é que você não declarou um relacionamento entre Vector2D e NamedVector. Você também não tem que escrever uma execução alternativa describeLength para o NamedVector. O sistema de tipo TypeScript simula o comportamento do tempo de execução do JavaScript (regra 1), o que permite que o NamedVector chame CalculeLength com base em sua estrutura sendo comparável ao Vector2D. Daí a expressão "tipagem estrutural".
Mas também pode causar problemas. Digamos que você adicione um tipo de vetor 3D:
interface Vector3D {
x: number;
y: number;
z: number;
}
e escreva uma função para normalizar os vetores (faça seu comprimento igual a 1):
function normalize(v: Vector3D) {
const length = calculateLength(v);
return {
x: v.x / length,
y: v.y / length,
z: v.z / length,
};
}
Se você chamar esta função, provavelmente obterá mais de um comprimento:
> normalize({x: 3, y: 4, z: 5})
{ x: 0.6, y: 0.8, z: 1 }
O que deu errado e por que o TypeScript não relatou um bug?
O bug é que calculeLength funciona com vetores 2D, enquanto normalizar funciona com 3D. Portanto, o componente z é ignorado durante a normalização.
Pode parecer estranho que o verificador de tipo não tenha percebido isso. Por que é permitido chamar calcularLength em um vetor 3D, embora seu tipo funcione com vetores 2D?
O que funcionou bem com o named saiu pela culatra aqui. Chamar calculeLength no objeto {x, y, z} não gerará um erro. Portanto, o módulo de verificação de tipo não reclama, o que acaba resultando em um bug. Se desejar que o erro seja detectado em tal caso, consulte a regra 37.
Ao escrever funções, é fácil imaginar que elas serão chamadas pelas propriedades que você declarou e nada mais. Isso é chamado de tipo "selado" ou "exato" e não pode ser aplicado no sistema de tipos TypeScript. Goste ou não, os tipos estão abertos aqui.
Às vezes, isso leva a surpresas:
function calculateLengthL1(v: Vector3D) {
let length = 0;
for (const axis of Object.keys(v)) {
const coord = v[axis];
// ~~~~~~~ "any",
// "string"
// "Vector3D"
length += Math.abs(coord);
}
return length;
}
Por que isso é um erro? Como o eixo é uma das chaves v de Vector3D, ele deve ser x, y ou z. E de acordo com a declaração do Vector3D original, são todos números. Portanto, o tipo de coordenação também não deveria ser um número?
Este não é um erro falso. Sabemos que Vector3D é estritamente definido e não possui outras propriedades. Embora ele pudesse:
const vec3D = {x: 3, y: 4, z: 1, address: '123 Broadway'};
calculateLengthL1(vec3D); // ok, NaN
Como v provavelmente pode ter qualquer propriedade, o eixo é do tipo string. Não há razão para o TypeScript pensar em v [eixo] apenas como um número. Ao iterar sobre objetos, pode ser difícil obter a digitação correta. Voltaremos a este tópico na Regra 54, mas por enquanto não usamos loops:
function calculateLengthL1(v: Vector3D) {
return Math.abs(v.x) + Math.abs(v.y) + Math.abs(v.z);
}
A tipagem estrutural também pode causar surpresas em classes que são comparadas para possíveis atribuições de propriedade:
class C {
foo: string;
constructor(foo: string) {
this.foo = foo;
}
}
const c = new C('instance of C');
const d: C = { foo: 'object literal' }; // ok!
Por que d pode ser atribuído a C? Ele tem uma propriedade foo que é string. Ele também tem um construtor (de Object.prototype) que pode ser chamado com um argumento (embora geralmente seja chamado sem ele). Portanto, as estruturas são as mesmas. Isso pode causar surpresas se você tiver lógica no construtor C e escrever uma função para invocá-lo. Esta é uma diferença significativa de linguagens como C ++ ou Java, onde as declarações de um parâmetro de tipo C garantem que ele pertence a C ou a uma subclasse dele.
A tipagem estrutural ajuda muito na hora de escrever testes. Digamos que você tenha uma função que executa uma consulta ao banco de dados e processa o resultado.
interface Author {
first: string;
last: string;
}
function getAuthors(database: PostgresDB): Author[] {
const authorRows = database.runQuery(`SELECT FIRST, LAST FROM
AUTHORS`);
return authorRows.map(row => ({first: row[0], last: row[1]}));
}
Para testá-lo, você pode criar um mock PostgresDB. No entanto, uma solução melhor seria usar a digitação estruturada e definir uma interface mais estreita:
interface DB {
runQuery: (sql: string) => any[];
}
function getAuthors(database: DB): Author[] {
const authorRows = database.runQuery(`SELECT FIRST, LAST FROM
AUTHORS`);
return authorRows.map(row => ({first: row[0], last: row[1]}));
}
Você ainda pode passar postgresDB para a função getAuthors na saída, já que ele tem um método runQuery. A tipagem estrutural não obriga o PostgresDB a relatar que está executando um banco de dados. O TypeScript resolverá isso para você.
Ao escrever testes, você também pode passar em um objeto mais simples:
test('getAuthors', () => {
const authors = getAuthors({
runQuery(sql: string) {
return [['Toni', 'Morrison'], ['Maya', 'Angelou']];
}
});
expect(authors).toEqual([
{first: 'Toni', last: 'Morrison'},
{first: 'Maya', last: 'Angelou'}
]);
});
O TypeScript determinará se o banco de dados de teste está em conformidade com a interface. Ao mesmo tempo, seus testes não precisam de nenhuma informação sobre o banco de dados de saída: nenhuma biblioteca de simulação é necessária. Ao introduzir a abstração (DB), liberamos a lógica dos detalhes de execução (PostgresDB).
Outra vantagem da tipagem estrutural é que ela pode quebrar claramente as dependências entre as bibliotecas. Para obter mais informações sobre este tópico, consulte a regra 51.
LEMBRE-SE que
JavaScript usa digitação duck e TypeScript modela-o usando digitação estruturada. Como resultado, os valores atribuídos às suas interfaces podem ter propriedades não especificadas nos tipos declarados. Os tipos no TypeScript não são lacrados.
Lembre-se de que as classes também obedecem às regras de tipagem estrutural. Portanto, você pode acabar com uma amostra de classe diferente do esperado.
Use a digitação estruturada para facilitar o teste de itens.
REGRA 5. Restringir o uso de qualquer tipo
O sistema de tipos no TypeScript é gradual e seletivo. Gradualidade se manifesta na capacidade de adicionar tipos ao código passo a passo e seletividade na capacidade de desativar o módulo de verificação de tipo quando necessário. A chave para controlar neste caso é qualquer tipo:
let age: number;
age = '12';
// ~~~ '"12"' 'number'.
age = '12' as any; // ok
O módulo de autorização, indicando um erro, mas pode ser evitado simplesmente adicionando como qualquer um. Quando você começa a trabalhar com o TypeScript, torna-se tentador usar quaisquer tipos ou afirmações se você não entender o erro, não confiar no validador ou não quiser perder tempo explicando os tipos. Mas lembre-se de que any nega muitos dos benefícios do TypeScript, a saber:
Reduz a segurança do código
No exemplo acima, de acordo com o tipo declarado, idade é número. Mas qualquer um permitiu que uma string fosse atribuída a ele. O validador presumirá que se trata de um número (que é como você o declarou), o que causará confusão.
age += 1; // ok. age "121".
Permite violar condições
Ao criar uma função, você define uma condição que, tendo recebido um determinado tipo de dados de uma chamada, produzirá o tipo correspondente na saída, que é violada assim:
function calculateAge(birthDate: Date): number {
// ...
}
let birthDate: any = '1990-01-19';
calculateAge(birthDate); // ok
O parâmetro birthDate deve ser Data, não string. O tipo qualquer permitia que a condição relacionada a describeAge fosse violada. Isso pode ser especialmente problemático porque o JavaScript tende a fazer conversões de tipo implícitas. Por causa disso, a string falhará em alguns casos em que o número deve falhar, mas inevitavelmente falhará em outros lugares.
Elimina o suporte para um serviço de linguagem
Quando um tipo é atribuído a um caractere, os serviços de linguagem TypeScript são capazes de fornecer substituição automática e documentação contextual apropriadas (Figura 1.3).

No entanto, ao atribuir o tipo any aos caracteres, você deve fazer tudo sozinho (Figura 1.4).

E renomeando também. Se você tiver um tipo de pessoa e funções para formatar o nome:
interface Person {
first: string;
last: string;
}
const formatName = (p: Person) => `${p.first} ${p.last}`;
const formatNameAny = (p: any) => `${p.first} ${p.last}`;
em seguida, você pode realçar primeiro no editor e selecionar Rename Symbol para renomeá-lo para firstName (Figura 1.5 e Figura 1.6).
Isso mudará a função formatName, mas não no caso de qualquer:
interface Person {
first: string;
last: string;
}
const formatName = (p: Person) => `${p.firstName} ${p.last}`;
const formatNameAny = (p: any) => `${p.first} ${p.last}`;

»Mais detalhes sobre o livro podem ser encontrados no site da editora
» Índice
» Trecho
Para Habitantes desconto de 25% no cupom - TypeScript No ato
do pagamento da versão em papel do livro, é enviado um e-book por e-mail.