Structure du livre
Le livre est une collection de courts essais (règles). Les règles sont regroupées en sections thématiques (chapitres), auxquelles il est possible d'accéder de manière autonome, en fonction de la question d'intérêt.
Chaque en-tête de règle contient une astuce, consultez donc la table des matières. Si, par exemple, vous écrivez de la documentation et avez des doutes sur l'écriture des informations de type, reportez-vous à la table des matières et à la règle 30 («Ne pas répéter les informations de type dans la documentation»).
Presque toutes les conclusions du livre sont illustrées par des exemples de code. Je pense que vous, comme moi, avez tendance à lire des livres techniques en regardant des exemples et en passant seulement par la partie du texte. Bien sûr, j'espère que vous avez lu attentivement les explications, mais j'ai couvert les principaux points des exemples.
Après avoir lu chaque astuce, vous pouvez comprendre exactement comment et pourquoi cela vous aidera à utiliser TypeScript plus efficacement. Vous comprendrez également s'il s'avère inutilisable d'une manière ou d'une autre. Je me souviens d'un exemple donné par Scott Myers, auteur de Effective C ++: les développeurs de logiciels de missiles auraient peut-être négligé les conseils sur la prévention des fuites de ressources parce que leurs programmes étaient détruits lorsque le missile touchait la cible. Je ne suis pas au courant de l'existence de fusées avec un système de contrôle écrit en JavaScript, mais un tel logiciel est disponible sur le télescope James Webb. Donc sois prudent.
Chaque règle se termine par un bloc "Doit se souvenir". En y jetant un coup d'œil, vous pouvez vous faire une idée générale du matériau et mettre en évidence l'essentiel. Mais je recommande vivement de lire la règle dans son intégralité.
Un extrait. RÈGLE 4. Habituez-vous au typage structurel
JavaScript est involontairement de type canard: si vous transmettez une valeur avec les propriétés correctes à une fonction, peu importe comment vous avez obtenu cette valeur. Elle l'utilise juste. TypeScript modélise ce comportement, ce qui conduit parfois à des résultats inattendus, car la compréhension des types par le validateur peut être plus large que la vôtre. Développer les compétences de frappe structurée vous permettra de mieux sentir où il y a vraiment des erreurs et d'écrire un code plus fiable.
Par exemple, vous travaillez avec une bibliothèque de physique et vous avez un type de vecteur 2D:
interface Vector2D {
x: number;
y: number;
}
Vous écrivez une fonction pour calculer sa longueur:
function calculateLength(v: Vector2D) {
return Math.sqrt(v.x * v.x + v.y * v.y);
}
et entrez la définition du vecteur nommé:
interface NamedVector {
name: string;
x: number;
y: number;
}
La fonction CalculateLength fonctionnera avec le NamedVector car il contient les propriétés x et y, qui sont des nombres. TypeScript comprend ceci:
const v: NamedVector = { x: 3, y: 4, name: 'Zee' };
calculateLength(v); // ok, 5.
La chose intéressante est que vous n'avez pas déclaré de relation entre Vector2D et NamedVector. Vous n'avez pas non plus besoin d'écrire une autre exécution de CalculateLength pour le NamedVector. Le système de type TypeScript simule le comportement d'exécution de JavaScript (règle 1), qui permettait au NamedVector d'appeler CalculateLength en fonction de sa structure comparable à Vector2D. D'où l'expression "typage structurel".
Mais cela peut aussi entraîner des problèmes. Disons que vous ajoutez un type de vecteur 3D:
interface Vector3D {
x: number;
y: number;
z: number;
}
et écrire une fonction pour normaliser les vecteurs (rendre leur longueur égale à 1):
function normalize(v: Vector3D) {
const length = calculateLength(v);
return {
x: v.x / length,
y: v.y / length,
z: v.z / length,
};
}
Si vous appelez cette fonction, vous obtiendrez probablement plus d'une longueur:
> normalize({x: 3, y: 4, z: 5})
{ x: 0.6, y: 0.8, z: 1 }
Qu'est-ce qui ne va pas et pourquoi TypeScript n'a-t-il pas signalé de bogue?
Le bogue est que CalculateLength fonctionne avec des vecteurs 2D, tandis que normalize fonctionne avec 3D. Par conséquent, le composant z est ignoré lors de la normalisation.
Il peut sembler étrange que le vérificateur de type n'ait pas détecté cela. Pourquoi est-il autorisé à appeler CalculateLength sur un vecteur 3D, même si son type fonctionne avec des vecteurs 2D?
Ce qui a bien fonctionné avec named s'est retourné contre lui. L'appel de CalculateLength sur l'objet {x, y, z} ne générera pas d'erreur. Par conséquent, le module de vérification de type ne se plaint pas, ce qui conduit finalement à l'apparition d'un bogue. Si vous souhaitez que l'erreur soit détectée dans ce cas, reportez-vous à la règle 37.
Lors de l'écriture de fonctions, il est facile d'imaginer qu'elles seront appelées par les propriétés que vous avez déclarées, et rien d'autre. Ceci est appelé un type «scellé» ou «exact» et ne peut pas être appliqué dans le système de type TypeScript. Qu'on le veuille ou non, les types sont ouverts ici.
Parfois, cela conduit à des surprises:
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;
}
Pourquoi est-ce une erreur? Puisque axis est l'une des clés v de Vector3D, il doit être x, y ou z. Et selon la déclaration originale de Vector3D, ce sont tous des nombres. Par conséquent, le type de coord ne devrait-il pas également être un nombre?
Ce n'est pas une fausse erreur. Nous savons que Vector3D est strictement défini et n'a pas d'autres propriétés. Bien qu'il puisse:
const vec3D = {x: 3, y: 4, z: 1, address: '123 Broadway'};
calculateLengthL1(vec3D); // ok, NaN
Puisque v pourrait probablement avoir des propriétés, axis est de type string. Il n'y a aucune raison pour que TypeScript considère v [axe] comme un simple nombre. Lors d'une itération sur des objets, il peut être difficile d'obtenir une saisie correcte. Nous reviendrons sur ce sujet dans la règle 54, mais pour l'instant nous n'utilisons pas de boucles:
function calculateLengthL1(v: Vector3D) {
return Math.abs(v.x) + Math.abs(v.y) + Math.abs(v.z);
}
Le typage structurel peut également provoquer des surprises dans les classes qui sont comparées pour d'éventuelles affectations de propriétés:
class C {
foo: string;
constructor(foo: string) {
this.foo = foo;
}
}
const c = new C('instance of C');
const d: C = { foo: 'object literal' }; // ok!
Pourquoi d peut-il être attribué à C? Il a une propriété foo qui est une chaîne. Il a également un constructeur (de Object.prototype) qui peut être appelé avec un argument (bien qu'il soit généralement appelé sans lui). Les structures sont donc les mêmes. Cela peut conduire à des surprises si vous avez une logique dans le constructeur C et que vous écrivez une fonction pour l'invoquer. C'est une différence significative par rapport aux langages comme C ++ ou Java, où les déclarations d'un paramètre de type C garantissent qu'il appartient à C ou à une sous-classe de celui-ci.
Le typage structurel aide beaucoup lors de l'écriture des tests. Supposons que vous ayez une fonction qui exécute une requête de base de données et traite le résultat.
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]}));
}
Pour le tester, vous pouvez créer une maquette PostgresDB. Cependant, une meilleure solution serait d'utiliser le typage structuré et de définir une interface plus étroite:
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]}));
}
Vous pouvez toujours transmettre postgresDB à la fonction getAuthors dans la sortie, car elle possède une méthode runQuery. Le typage structurel n'oblige pas PostgresDB à signaler qu'il exécute un DB. TypeScript le découvrira pour vous.
Lors de l'écriture de tests, vous pouvez également passer un objet plus simple:
test('getAuthors', () => {
const authors = getAuthors({
runQuery(sql: string) {
return [['Toni', 'Morrison'], ['Maya', 'Angelou']];
}
});
expect(authors).toEqual([
{first: 'Toni', last: 'Morrison'},
{first: 'Maya', last: 'Angelou'}
]);
});
TypeScript déterminera que le DB de test est conforme à l'interface. Dans le même temps, vos tests ne nécessitent aucune information sur la base de données de sortie: aucune bibliothèque fictive n'est requise. En introduisant l'abstraction (DB), nous avons libéré la logique des détails d'exécution (PostgresDB).
Un autre avantage du typage structurel est qu'il peut clairement briser les dépendances entre bibliothèques. Pour plus d'informations sur ce sujet, consultez la règle 51.
RAPPELEZ-VOUS que
JavaScript utilise le typage canard et TypeScript le modélise en utilisant un typage structuré. Par conséquent, les valeurs affectées à vos interfaces peuvent avoir des propriétés non spécifiées dans les types déclarés. Les types dans TypeScript ne sont pas scellés.
Gardez à l'esprit que les classes obéissent également aux règles de typage structurel. Par conséquent, vous pouvez vous retrouver avec un échantillon de classe différent de celui attendu.
Utilisez la saisie structurée pour faciliter le test des éléments.
RÈGLE 5. Restreindre l'utilisation des types
Le système de type de TypeScript est progressif et sélectif. La gradualité se manifeste dans la possibilité d'ajouter des types au code étape par étape, et la sélectivité dans la possibilité de désactiver le module de vérification de type lorsque vous en avez besoin. La clé à contrôler dans ce cas est le type any:
let age: number;
age = '12';
// ~~~ '"12"' 'number'.
age = '12' as any; // ok
Le module de droit, indiquant une erreur, mais il peut être évité en ajoutant simplement comme any. Lorsque vous commencez à travailler avec TypeScript, il devient tentant d'utiliser des types ou des assertions si vous ne comprenez pas l'erreur, ne faites pas confiance au validateur ou ne voulez pas passer du temps à épeler les types. Mais rappelez-vous que tout annule de nombreux avantages de TypeScript, à savoir:
Diminue la sécurité du code
Dans l'exemple ci-dessus, selon le type déclaré, l'âge est le nombre. Mais tout permettait de lui attribuer une chaîne. Le validateur supposera qu'il s'agit d'un nombre (ce que vous avez déclaré), ce qui prêtera à confusion.
age += 1; // ok. age "121".
Vous permet de violer les conditions
Lors de la création d'une fonction, vous définissez une condition qui, après avoir reçu un certain type de données d'un appel, produira le type correspondant dans la sortie, qui est violé comme ceci:
function calculateAge(birthDate: Date): number {
// ...
}
let birthDate: any = '1990-01-19';
calculateAge(birthDate); // ok
Le paramètre birthDate doit être Date et non chaîne. Le type any permettait de violer la condition relative à CalculateAge. Cela peut être particulièrement problématique car JavaScript a tendance à effectuer des conversions de type implicites. Pour cette raison, la chaîne échouera dans certains cas où nombre est censé échouer, mais échouera inévitablement ailleurs.
Élimine la prise en charge d'un service de langage
Lorsqu'un type est attribué à un caractère, les services de langage TypeScript sont en mesure de fournir une auto-substitution appropriée et une documentation contextuelle (Figure 1.3).
Cependant, en attribuant le type any aux caractères, vous devez tout faire vous-même (Figure 1.4).
Et renommer aussi. Si vous avez un type de personne et des fonctions pour formater le nom:
interface Person {
first: string;
last: string;
}
const formatName = (p: Person) => `${p.first} ${p.last}`;
const formatNameAny = (p: any) => `${p.first} ${p.last}`;
Ensuite, vous pouvez d'abord mettre en surbrillance dans l'éditeur et sélectionner le symbole Renommer pour le renommer en firstName (Figure 1.5 et Figure 1.6).
Cela changera la fonction formatName, mais pas dans le cas de:
interface Person {
first: string;
last: string;
}
const formatName = (p: Person) => `${p.firstName} ${p.last}`;
const formatNameAny = (p: any) => `${p.first} ${p.last}`;
»Plus de détails sur le livre peuvent être trouvés sur le site de la maison d'édition
» Table des matières
» Extrait
Pour Habitants un rabais de 25% sur le coupon - TypeScript
Lors du paiement de la version papier du livre, un e-book est envoyé par e-mail.