Recherche de site Web

Comment créer des types personnalisés en TypeScript ?


L'auteur a choisi le Fonds de secours COVID-19 pour recevoir un don dans le cadre du programme Écrire pour des dons.

Introduction

TypeScript est une extension du langage JavaScript qui utilise le runtime de JavaScript avec un vérificateur de type à la compilation. Cette combinaison permet aux développeurs d'utiliser l'ensemble de l'écosystème et des fonctionnalités du langage JavaScript, tout en ajoutant en plus une vérification de type statique optionnelle, des énumérations, des classes et des interfaces.

Bien que les types de base préfabriqués dans TypeScript couvrent de nombreux cas d'utilisation, créer vos propres types personnalisés basés sur ces types de base vous permettra de garantir que le vérificateur de types valide les structures de données spécifiques à votre projet. Cela réduira les risques de bogues dans votre projet, tout en permettant une meilleure documentation des structures de données utilisées dans le code.

Ce tutoriel vous montrera comment utiliser des types personnalisés avec TypeScript, comment combiner ces types avec des unions et des intersections, et comment utiliser les types utilitaires pour ajouter de la flexibilité à vos types personnalisés. Il vous guidera à travers différents exemples de code, que vous pouvez suivre dans votre propre environnement TypeScript ou dans le TypeScript Playground, un environnement en ligne qui vous permet d'écrire TypeScript directement dans le navigateur.

Au-delà du TypeScript de base, les assistants de codage IA modernes comme GitHub Copilot et Codeium peuvent accélérer la définition et l'application des types à grande échelle. Dans ce tutoriel, vous associez des complétions et des refactorisations assistées par l'IA à des options strictes du compilateur, la validation à l'exécution, la génération de code à partir des schémas d'API, des contrôles de gouvernance et l'automatisation CI pour maintenir de manière fiable la sécurité des types dans de grands projets et monorepos.

Points clés :

  • Types personnalisés : Définissez des formes d'objet avec type, ajoutez des propriétés optionnelles (?) et utilisez des signatures d'index ou Record pour des mappages clé/valeur flexibles.
  • Types de composition : Créez des unions puissantes (|) et des intersections (&), et imposez l'exhaustivité avec des unions discriminantes et des vérifications never dans les instructions switch.
  • Tableaux et tuples : Modélisez « au moins N éléments » en utilisant des tuples avec des éléments restants (par exemple, [string, string, ...string[]]).
  • Types littéraux de modèle : Contraignez les formats de chaîne avec des types basés sur les backticks (par exemple, ``type Id=`user_$ {number}```).
  • Assertions de type et restriction : Préférez les gardes de type (value is T), in/instanceof, et l'analyse du flux de contrôle plutôt que les assertions as.
  • Types utilitaires : Appliquez Record, Pick, Omit et Partial pour transformer des structures sans réécrire les types.
  • IA pour TypeScript à grande échelle : Utilisez GitHub Copilot et Codeium pour générer des types à partir de JSON, suggérer des unions discriminantes et refactorer vers la stricte ; associez-le à une validation à l'exécution (par exemple, zod) pour appliquer la forme des données aux limites.
  • Pipeline de génération de code : Générer et versionner les types d'API à partir de OpenAPI/GraphQL (openapi-typescript, @graphql-codegen/cli), et bloquer les fusions lorsque les fichiers générés divergent.
  • Configuration du projet : Activer les options strictes de TypeScript (strict, noImplicitAny, strictNullChecks, exactOptionalPropertyTypes, noUncheckedIndexedAccess), et appliquer l'hygiène avec les règles typescript-eslint.
  • Gouvernance et confidentialité : Adoptez une liste de contrôle de gouvernance et un manifeste YAML (modèles approuvés, régions, conservation, filtres de code public), et limitez les requêtes à ne pas inclure de secrets/ données personnelles identifiables (PII).
  • Automatisation et CI : Ajouter tsc --noEmit et ESLint à la CI, et des hooks de pré-commit pour exécuter codegen et validate afin de maintenir les types synchronisés.

Prérequis

Pour suivre ce tutoriel, vous aurez besoin de :

  • Un environnement dans lequel vous pouvez exécuter des programmes TypeScript pour suivre les exemples. Pour configurer cela sur votre machine locale, vous aurez besoin des éléments suivants :

    • Node et npm (ou yarn) doivent être installés afin d'exécuter un environnement de développement qui gère les packages liés à TypeScript. Ce tutoriel a été testé avec Node.js 20 LTS et npm 10.x. Pour installer sur macOS ou Ubuntu, suivez les étapes dans Comment installer Node.js et créer un environnement de développement local sur macOS ou le guide d'installation Linux pour votre distribution. Cela fonctionne également si vous utilisez le Sous-système Windows pour Linux (WSL).
    • De plus, vous aurez besoin du compilateur TypeScript (tsc) installé sur votre machine. Pour ce faire, consultez le site officiel de TypeScript.
  • Si vous ne souhaitez pas créer un environnement TypeScript sur votre machine locale, vous pouvez utiliser le TypeScript Playground officiel pour suivre.

  • Vous aurez besoin de connaissances suffisantes en JavaScript, en particulier la syntaxe ES6+, comme la déstructuration, les opérateurs rest et les imports/exports. Si vous avez besoin de plus d'informations sur ces sujets, il est recommandé de lire notre série Comment coder en JavaScript.

  • Ce tutoriel fera référence à des aspects des éditeurs de texte et des outils d'IA qui prennent en charge TypeScript et affichent les erreurs en ligne. Ce n'est pas nécessaire pour utiliser TypeScript, mais cela permet de tirer davantage parti des fonctionnalités de TypeScript. Pour bénéficier de cela, vous pouvez utiliser un éditeur de texte comme Visual Studio Code, qui offre une prise en charge complète de TypeScript dès l'installation. De plus, des outils d'IA comme GitHub Copilot et Codeium peuvent accélérer la définition et l'application des types à grande échelle. Vous pouvez également essayer ces avantages dans le TypeScript Playground.

  • Démarrage rapide optionnel (outils de développement) :

npm i -D typescript eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin zod openapi-typescript @graphql-codegen/cli husky lint-staged

Tous les exemples montrés dans ce tutoriel ont été créés en utilisant TypeScript 5.x.

Créer des types personnalisés

Dans les cas où les programmes ont des structures de données complexes, utiliser les types de base de TypeScript peut ne pas décrire complètement les structures de données que vous utilisez. Dans ces cas, déclarer votre propre type vous aidera à gérer la complexité. Dans cette section, vous allez créer des types qui peuvent être utilisés pour décrire n'importe quelle forme d'objet que vous devez utiliser dans votre code.

Syntaxe de type personnalisé

En TypeScript, la syntaxe pour créer des types personnalisés consiste à utiliser le mot-clé type suivi du nom du type, puis d'une affectation à un bloc {} contenant les propriétés du type. Prenons l'exemple suivant :

type Programmer = {
  name: string;
  knownFor: string[];
};

La syntaxe ressemble à un littéral d'objet, où la clé est le nom de la propriété et la valeur est le type que cette propriété doit avoir. Cela définit un type Programmer qui doit être un objet avec la clé name qui contient une valeur de type chaîne et une clé knownFor qui contient un tableau de chaînes.

Comme montré dans l'exemple précédent, vous pouvez utiliser ; comme séparateur entre chaque propriété. Il est également possible d'utiliser une virgule, ,, ou de supprimer complètement le séparateur, comme montré ici :

type Programmer = {
  name: string
  knownFor: string[]
};

Utiliser votre type personnalisé est la même chose que d'utiliser l'un des types de base. Ajoutez un deux-points puis ajoutez le nom de votre type :

type Programmer = {
  name: string;
  knownFor: string[];
};

const ada: Programmer = {
  name: 'Ada Lovelace',
  knownFor: ['Mathematics', 'Computing', 'First Programmer']
};

La constante ada passera maintenant le vérificateur de type sans générer d'erreur.

Si vous écrivez cet exemple dans n'importe quel éditeur avec un support complet de TypeScript, comme dans le TypeScript Playground, l'éditeur suggérera les champs attendus par cet objet et leurs types, comme le montre l'animation suivante :

Si vous ajoutez des commentaires aux champs en utilisant le format TSDoc, un style populaire de documentation de commentaires TypeScript, ils sont également suggérés dans l'autocomplétion. Prenez le code suivant avec des explications dans les commentaires :

type Programmer = {
  /**
   * The full name of the Programmer
   */
  name: string;
  /**
   * This Programmer is known for what?
   */
  knownFor: string[];
};

const ada: Programmer = {
  name: 'Ada Lovelace',
  knownFor: ['Mathematics', 'Computing', 'First Programmer']
};

Les descriptions commentées apparaîtront désormais avec les suggestions de champ :

Lors de la création d'un objet avec le type personnalisé Programmer, si vous assignez une valeur d'un type inattendu à l'une des propriétés, TypeScript générera une erreur. Prenez le bloc de code suivant, avec une ligne en surbrillance qui ne respecte pas la déclaration de type :

type Programmer = {
  name: string;
  knownFor: string[];
};

const ada: Programmer = {
  name: true,
  knownFor: ['Mathematics', 'Computing', 'First Programmer']
};

Le compilateur TypeScript (tsc) affichera l'erreur 2322:

Type 'boolean' is not assignable to type 'string'. (2322)

Si vous avez omis l'une des propriétés requises par votre type, comme dans l'exemple suivant :

type Programmer = {
  name: string;
  knownFor: string[];
};

const ada: Programmer = {
  name: 'Ada Lovelace'
};

Le compilateur TypeScript donnera l'erreur 2741 :

Property 'knownFor' is missing in type '{ name: string; }' but required in type 'Programmer'. (2741)

Ajouter une nouvelle propriété non spécifiée dans le type original entraînera également une erreur :

type Programmer = {
  name: string;
  knownFor: string[];
};

const ada: Programmer = {
  name: "Ada Lovelace",
  knownFor: ['Mathematics', 'Computing', 'First Programmer'],
  age: 36
};

Dans ce cas, l'erreur affichée est le 2322 :

Type '{ name: string; knownFor: string[]; age: number; }' is not assignable to type 'Programmer'.
Object literal may only specify known properties, and 'age' does not exist in type 'Programmer'.(2322)

Types personnalisés imbriqués

Vous pouvez également imbriquer ensemble des types personnalisés. Imaginez que vous ayez un type Company qui a un champ manager conforme à un type Person. Vous pourriez créer ces types comme ceci :

type Person = {
  name: string;
};

type Company = {
  name: string;
  manager: Person;
};

Then you could create a value of type Company like this:

const manager: Person = {
  name: 'John Doe',
}

const company: Company = {
  name: 'ACME',
  manager,
}

Ce code passerait le vérificateur de type, puisque la constante manager correspond au type désigné pour le champ manager. Notez que ceci utilise la syntaxe abrégée des propriétés d'objet pour déclarer manager.

Vous pouvez omettre le type dans la constante manager car elle a la même forme que le type Person. TypeScript ne va pas générer d'erreur lorsque vous utilisez un objet ayant la même forme que celle attendue par le type de la propriété manager, même s'il n'est pas explicitement défini pour avoir le type Person.

Ce qui suit ne provoquera pas d'erreur :

const manager = {
  name: 'John Doe'
}

const company: Company = {
  name: 'ACME',
  manager
}

Vous pouvez même aller un pas plus loin et définir le manager directement à l'intérieur de ce littéral d'objet company :

const company: Company = {
  name: 'ACME',
  manager: {
    name: 'John Doe'
  }
};

Tous ces scénarios sont valides.

Si vous écrivez ces exemples dans un éditeur qui prend en charge TypeScript, vous constaterez que l'éditeur utilisera les informations de type disponibles pour se documenter lui-même. Pour l'exemple précédent, dès que vous ouvrez le littéral d'objet {} pour manager, l'éditeur s'attendra à une propriété name de type string :

Maintenant que vous avez passé en revue quelques exemples de création de votre propre type personnalisé avec un nombre fixe de propriétés, vous allez ensuite essayer d'ajouter des propriétés optionnelles à vos types.

Propriétés optionnelles

Avec la déclaration de type personnalisé dans les sections précédentes, vous ne pouvez omettre aucune des propriétés lors de la création d'une valeur de ce type. Il existe cependant certains cas qui nécessitent des propriétés optionnelles pouvant passer le vérificateur de type avec ou sans la valeur. Dans cette section, vous allez déclarer ces propriétés optionnelles.

Pour ajouter des propriétés optionnelles à un type, ajoutez le modificateur ? à la propriété. En utilisant le type Programmer des sections précédentes, transformez la propriété knownFor en propriété optionnelle en ajoutant le caractère surligné suivant :

type Programmer = {
  name: string;
  knownFor?: string[];
};

Ici, vous ajoutez le modificateur ? après le nom de la propriété. Cela fait que TypeScript considère cette propriété comme optionnelle et ne génère pas d'erreur lorsque vous omettez cette propriété :

type Programmer = {
  name: string;
  knownFor?: string[];
};

const ada: Programmer = {
  name: 'Ada Lovelace'
};

Cela passera sans erreur.

Maintenant que vous savez comment ajouter des propriétés optionnelles à un type, il est temps d'apprendre à créer un type qui peut contenir un nombre illimité de champs.

Types indexables

Les exemples précédents ont montré que vous ne pouvez pas ajouter de propriétés à une valeur d'un type donné si ce type ne spécifie pas ces propriétés lors de sa déclaration. Dans cette section, vous allez créer des types indexables, qui sont des types permettant un nombre quelconque de champs s'ils suivent la signature d'index du type.

Imaginez que vous ayez un type Data pour contenir un nombre illimité de propriétés du type any. Vous pourriez déclarer ce type ainsi :

type Data = {
  [key: string]: any;
};

Ici, vous créez un type normal avec le bloc de définition de type entre accolades ({}), puis vous ajoutez une propriété spéciale au format [key: typeOfKeys]: typeOfValues, où typeOfKeys est le type que les clés de cet objet doivent avoir, et typeOfValues est le type que les valeurs de ces clés doivent avoir.

Vous pouvez ensuite l'utiliser normalement comme tout autre type :

type Data = {
  [key: string]: any;
};

const someData: Data = {
  someBooleanKey: true,
  someStringKey: 'text goes here'
  // ...
}

En utilisant des types indexables, vous pouvez attribuer un nombre illimité de propriétés, tant qu'elles correspondent à la signature d'index, qui est le nom utilisé pour décrire les types des clés et des valeurs d'un type indexable. Dans ce cas, les clés ont un type string et les valeurs ont un type any.

Il est également possible d'ajouter des propriétés spécifiques qui sont toujours obligatoires à votre type indexable, tout comme vous pourriez le faire avec un type normal. Dans le code surligné suivant, vous ajoutez la propriété status à votre type Data :

type Data = {
  status: boolean;
  [key: string]: any;
};

const someData: Data = {
  status: true,
  someBooleanKey: true,
  someStringKey: 'text goes here'
  // ...
}

Cela signifierait qu'un objet de type Data doit avoir une clé status avec une valeur boolean pour passer le vérificateur de type.

Maintenant que vous pouvez créer un objet avec différents nombres d'éléments, vous pouvez passer à l'apprentissage des tableaux en TypeScript, qui peuvent avoir un nombre personnalisé d'éléments ou plus.

Créer des tableaux avec le nombre d'éléments ou plus

En utilisant à la fois les types de base array et tuple disponibles dans TypeScript, vous pouvez créer des types personnalisés pour les tableaux qui devraient avoir un nombre minimum d'éléments. Dans cette section, vous utiliserez l'opérateur rest de TypeScript ... pour ce faire.

Imaginez que vous avez une fonction responsable de la fusion de plusieurs chaînes. Cette fonction va prendre un seul paramètre de type tableau. Ce tableau doit contenir au moins deux éléments, chacun devant être une chaîne. Vous pouvez créer un type comme celui-ci avec ce qui suit :

type MergeStringsArray = [string, string, ...string[]];

Le type MergeStringsArray profite du fait que vous pouvez utiliser l'opérateur de rest avec un type de tableau et utilise le résultat de cela comme le troisième élément d'un tuple. Cela signifie que les deux premières chaînes sont obligatoires, mais que les éléments de chaîne supplémentaires après cela ne sont pas obligatoires.

Si un tableau contient moins de deux éléments de type chaîne, il sera invalide, comme suit :

const invalidArray: MergeStringsArray = ['some-string']

Le compilateur TypeScript va générer l'erreur 2322 lors de la vérification de ce tableau :

Type '[string]' is not assignable to type 'MergeStringsArray'.
Source has 1 element(s) but target requires 2. (2322)

Jusqu'à présent, vous avez créé vos propres types personnalisés à partir d'une combinaison de types de base. Dans la section suivante, vous allez créer un nouveau type en composant deux types personnalisés ou plus ensemble.

Types de composition

Cette section expliquera deux façons de composer des types ensemble. Elles utiliseront l'opérateur d'union pour passer des données qui respectent l'un ou l'autre type et l'opérateur d'intersection pour passer des données qui satisfont toutes les conditions des deux types.

Syndicats

Les unions sont créées en utilisant l'opérateur | (pipe), qui représente une valeur pouvant avoir n'importe lequel des types de l'union. Prenons l'exemple suivant :

type ProductCode = number | string

Dans ce code, ProductCode peut être soit un string, soit un number. Le code suivant passera le vérificateur de types :

type ProductCode = number | string;

const productCodeA: ProductCode = 'this-works';

const productCodeB: ProductCode = 1024;

Un type union peut être créé à partir d'une union de n'importe quels types valides de TypeScript.

Intersections

Vous pouvez utiliser les types d'intersection pour créer un type entièrement nouveau qui possède toutes les propriétés de tous les types étant combinés ensemble.

Par exemple, imaginez que vous avez certains champs communs qui apparaissent toujours dans la réponse de vos appels API, puis des champs spécifiques pour certains points de terminaison :

type StatusResponse = {
  status: number;
  isValid: boolean;
};

type User = {
  name: string;
};

type GetUserResponse = {
  user: User;
};

Dans ce cas, toutes les réponses auront les propriétés status et isValid, mais seules les réponses des utilisateurs auront le champ supplémentaire user. Pour créer la réponse résultante d'un appel API utilisateur spécifique en utilisant un type d'intersection, combinez à la fois les types StatusResponse et GetUserResponse :

type ApiGetUserResponse = StatusResponse & GetUserResponse;

Le type ApiGetUserResponse aura toutes les propriétés disponibles dans StatusResponse et celles disponibles dans GetUserResponse. Cela signifie que les données ne passeront le vérificateur de type que si elles satisfont toutes les conditions des deux types. L'exemple suivant fonctionnera :

let response: ApiGetUserResponse = {
    status: 200,
    isValid: true,
    user: {
        name: 'Sammy'
    }
}

Un autre exemple serait le type des lignes renvoyées par un client de base de données pour une requête contenant des jointures. Vous seriez capable d'utiliser un type d'intersection pour spécifier le résultat d'une telle requête :

type UserRoleRow = {
  role: string;
}

type UserRow = {
  name: string;
};

type UserWithRoleRow = UserRow & UserRoleRow;

Plus tard, si vous utilisez une fonction fetchRowsFromDatabase() comme suit :

const joinedRows: UserWithRoleRow = fetchRowsFromDatabase()

La constante résultante joinedRows doit avoir une propriété role et une propriété name qui contiennent toutes deux des valeurs de type chaîne de caractères afin de passer le vérificateur de types.

Utilisation des types de chaînes de modèle

À partir de TypeScript 4.1, il est possible de créer des types en utilisant des types de chaînes de template. Cela vous permettra de créer des types qui vérifient des formats de chaînes spécifiques et d'ajouter plus de personnalisation à votre projet TypeScript.

Pour créer des types de chaînes de modèle, vous utilisez une syntaxe qui est presque la même que celle que vous utiliseriez pour créer des littéraux de chaînes de modèle. Mais au lieu de valeurs, vous utiliserez d'autres types à l'intérieur du modèle de chaîne.

Imaginez que vous vouliez créer un type qui accepte toutes les chaînes qui commencent par get. Vous seriez capable de le faire en utilisant des types de chaînes de modèles :

type StringThatStartsWithGet = `get${string}`;

const myString: StringThatStartsWithGet = 'getAbc';

myString réussira la vérification de type ici parce que la chaîne commence par get puis est suivie d'une chaîne supplémentaire.

Si vous avez passé une valeur non valide à votre type, comme le suivant invalidStringValue :

type StringThatStartsWithGet = `get${string}`;

const invalidStringValue: StringThatStartsWithGet = 'something';

Le compilateur TypeScript vous donnerait l'erreur 2322 :

Type '"something"' is not assignable to type '`get${string}`'. (2322)

Créer des types avec des chaînes de modèles vous aide à personnaliser votre type en fonction des besoins spécifiques de votre projet. Dans la section suivante, vous allez essayer les assertions de type, qui ajoutent un type à des données autrement non typées.

Utilisation des assertions de type

Le type any peut être utilisé comme le type de n'importe quelle valeur, ce qui ne fournit souvent pas la typage strict nécessaire pour tirer pleinement parti de TypeScript. Mais parfois, vous pouvez vous retrouver avec certaines variables liées à any qui sont hors de votre contrôle. Cela se produira si vous utilisez des dépendances externes qui n'ont pas été écrites en TypeScript ou qui n'ont pas de déclaration de type disponible.

Au cas où vous voudriez rendre votre code sûr au niveau des types dans ces scénarios, vous pouvez utiliser des assertions de type, qui sont un moyen de changer le type d'une variable en un autre type. Les assertions de type sont rendues possibles en ajoutant as NouveauType après votre variable. Cela changera le type de la variable en celui spécifié après le mot-clé as.

Prenez l'exemple suivant :

const valueA: any = 'something';

const valueB = valueA as string;

valueA a le type any, mais, en utilisant le mot-clé as, ce code force valueB à avoir le type string.

Remarque : Pour affirmer qu'une variable de TypeA ait le type TypeB, TypeB doit être un sous-type de TypeA. Presque tous les types TypeScript, à l'exception de never, sont des sous-types de any, y compris unknown.

Types utilitaires

Dans les sections précédentes, vous avez examiné plusieurs façons de créer des types personnalisés à partir de types de base. Mais parfois, vous ne voulez pas créer un type complètement nouveau à partir de zéro. Il y a des moments où il peut être préférable d'utiliser quelques propriétés d'un type existant, ou même de créer un nouveau type qui a la même forme qu'un autre type, mais avec toutes les propriétés définies comme optionnelles.

Tout cela est possible en utilisant les types utilitaires existants disponibles avec TypeScript. Cette section couvrira quelques-uns de ces types utilitaires ; pour une liste complète de tous ceux disponibles, consultez la partie Types utilitaires du manuel TypeScript.

Tous les types utilitaires sont des types génériques, que vous pouvez considérer comme un type qui accepte d'autres types en tant que paramètres. Un type générique peut être identifié par la possibilité de lui passer des paramètres de type en utilisant la syntaxe <TypeA, TypeB, ...>.

Enregistrement<Clé, Valeur>

Le type utilitaire Record peut être utilisé pour créer un type indexable de manière plus propre que l'utilisation de la signature d'index abordée précédemment.

Dans votre exemple de types indexables, vous aviez le type suivant :

type Data = {
  [key: string]: any;
};

Vous pouvez utiliser le type utilitaire Record au lieu d’un type indexable comme ceci :

type Data = Record<string, any>;

Le premier paramètre de type du générique Record est le type de chaque clé. Dans l'exemple suivant, toutes les clés doivent être des chaînes :

type Data = Record<string, any>

Le deuxième paramètre de type est le type de chaque value de ces clés. Ce qui suit permettrait aux valeurs d'être any :

type Data = Record<string, any>

Omit<Type, Fields>

Le type utilitaire Omit est utile pour créer un nouveau type basé sur un autre, tout en excluant certaines propriétés que vous ne voulez pas dans le type résultant.

Imaginez que vous avez le type suivant pour représenter le type d'une ligne utilisateur dans une base de données :

type UserRow = {
  id: number;
  name: string;
  email: string;
  addressId: string;
};

Si dans votre code vous récupérez tous les champs sauf celui de addressId, vous pouvez utiliser Omit pour créer un nouveau type sans ce champ :

type UserRow = {
  id: number;
  name: string;
  email: string;
  addressId: string;
};

type UserRowWithoutAddressId = Omit<UserRow, 'addressId'>;

Le premier argument de Omit est le type sur lequel vous basez le nouveau type. Le second est le champ que vous souhaitez omettre.

Si vous survolez UserRowWithoutAddressId dans votre éditeur de code, vous constaterez qu'il possède toutes les propriétés du type UserRow sauf celles que vous avez omises.

Vous pouvez passer plusieurs champs au deuxième paramètre de type en utilisant une union de chaînes. Disons que vous vouliez également omettre le champ id, vous pourriez faire ceci :

type UserRow = {
  id: number;
  name: string;
  email: string;
  addressId: string;
};

type UserRowWithoutIds = Omit<UserRow, 'id' | 'addressId'>;

Pick<Type, Fields>

Le type utilitaire Pick est exactement l'opposé du type Omit. Au lieu d'indiquer les champs que vous souhaitez omettre, vous spécifiez les champs que vous souhaitez utiliser à partir d'un autre type.

En utilisant le même UserRow que vous avez utilisé précédemment :

type UserRow = {
  id: number;
  name: string;
  email: string;
  addressId: string;
};

Imaginez que vous devez sélectionner uniquement la clé email à partir de la ligne de la base de données. Vous pourriez créer un tel type en utilisant Pick comme ceci :

type UserRow = {
  id: number;
  name: string;
  email: string;
  addressId: string;
};

type UserRowWithEmailOnly = Pick<UserRow, 'email'>;

Le premier argument de Pick ici spécifie le type sur lequel vous basez le nouveau type. Le second est la clé que vous souhaitez inclure.

Cela serait équivalent à ce qui suit :

type UserRowWithEmailOnly = {
    email: string;
}

Vous pouvez également sélectionner plusieurs champs en utilisant une union de chaînes :

type UserRow = {
  id: number;
  name: string;
  email: string;
  addressId: string;
};

type UserRowWithEmailOnly = Pick<UserRow, 'name' | 'email'>;

Partiel<Type>

En utilisant le même exemple UserRow, imaginez que vous voulez créer un nouveau type qui correspond à l'objet que votre client de base de données peut utiliser pour insérer de nouvelles données dans votre table utilisateur, mais avec un petit détail : votre base de données a des valeurs par défaut pour tous les champs, donc vous n'êtes pas obligé de passer l'un d'entre eux. Pour ce faire, vous pouvez utiliser un type utilitaire Partial pour inclure éventuellement tous les champs du type de base.

Votre type existant, UserRow, possède toutes les propriétés requises :

type UserRow = {
  id: number;
  name: string;
  email: string;
  addressId: string;
};

Pour créer un nouveau type où toutes les propriétés sont optionnelles, vous pouvez utiliser le type utilitaire Partial<Type> comme suit :

type UserRow = {
  id: number;
  name: string;
  email: string;
  addressId: string;
};

type UserRowInsert = Partial<UserRow>;

Ceci est exactement la même chose que d'avoir votre UserRowInsert comme ceci :

type UserRow = {
  id: number;
  name: string;
  email: string;
  addressId: string;
};

type UserRowInsert = {
  id?: number | undefined;
  name?: string | undefined;
  email?: string | undefined;
  addressId?: string | undefined;
};

Les types utilitaires sont une excellente ressource à avoir, car ils offrent un moyen plus rapide de créer des types que de les créer à partir des types de base en TypeScript.

Utiliser des outils d'IA pour définir et appliquer des types à grande échelle

À mesure que l'adoption de TypeScript croît dans de grandes bases de code, les assistants de codage IA comme GitHub Copilot et Codeium peuvent accélérer la façon dont les équipes définissent, refactorisent et appliquent les types de manière cohérente.

Flux de travail pratiques

  • Générez des types à partir d'exemples : Collez des JSON ou des payloads d'API exemples et invitez : Créez des types TypeScript stricts et un schéma Zod pour ce payload. L'IA peut produire à la fois des types à la compilation et des validateurs à l'exécution.
  • Refactoriser vers la stricte : Lors de l'activation de "strict": true (y compris noImplicitAny, strictNullChecks), l'IA peut suggérer des migrations sûres de any vers des unions précises, des types marqués et unknown avec un resserrement de type.
  • Unions discriminantes et exhaustivité : À partir des instructions switch, l'IA peut déduire les discriminants et ajouter des vérifications never pour garantir l'exhaustivité dans les réductions et les machines à états.
  • Échafaudage de schéma API : Avec les schémas OpenAPI/GraphQL, l'IA peut aider à connecter les sorties de génération de code (par exemple, openapi-typescript, graphql-codegen) aux types du projet et aux sites d'utilisation.
  • Validation à l'exécution : l'IA peut associer des types à des validateurs (par exemple, zod) pour appliquer la structure aux frontières :
import { z } from 'zod';

// Example JSON from an API
const payload = {
  id: 'ord_123',
  totalCents: 2599,
  status: 'paid',
  items: [{ sku: 'sku-1', qty: 2 }],
};

// AI-suggested types and schema
const OrderItemSchema = z.object({
  sku: z.string(),
  qty: z.number().int().positive(),
});

const OrderSchema = z.object({
  id: z.string(),
  totalCents: z.number().int().nonnegative(),
  status: z.enum(['pending', 'paid', 'failed']),
  items: z.array(OrderItemSchema).min(1),
});

type Order = z.infer<typeof OrderSchema>;

function parseOrder(input: unknown): Order {
  return OrderSchema.parse(input);
}

const order = parseOrder(payload);

Configuration recommandée pour les grands projets

  • tsconfig.json : Activez la rigueur pour maximiser l’utilité des suggestions de l’IA.
{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true
  }
}
  • ESLint avec typescript-eslint : Faites respecter l'hygiène des types et laissez l'IA corriger automatiquement de nombreux problèmes.
// .eslintrc.cjs
module.exports = {
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:@typescript-eslint/recommended-type-checked',
  ],
  parser: '@typescript-eslint/parser',
  parserOptions: { project: ['./tsconfig.json'] },
  rules: {
    '@typescript-eslint/no-explicit-any': 'warn',
    '@typescript-eslint/no-unsafe-assignment': 'error',
    '@typescript-eslint/consistent-type-definitions': ['error', 'type'],
    '@typescript-eslint/no-floating-promises': 'error'
  }
};
  • Configuration de l'éditeur : Dans VS Code, utilisez la version TypeScript de l’espace de travail et activez les extensions IA. Gardez TypeScript à la version 5.x pour accéder aux fonctionnalités modernes du système de types que l'IA utilise dans ses suggestions.
  • CI : Ajouter des vérifications rapides et non émettrices afin que les régressions de type fassent échouer les builds.
// package.json (scripts)
{
  "scripts": {
    "typecheck": "tsc -p tsconfig.json --noEmit",
    "lint": "eslint . --ext .ts,.tsx",
    "validate": "npm run typecheck && npm run lint"
  }
}

Monorepo et conseils pour le scaling

  • Activez les références de projet TypeScript pour des compilations plus rapides et des limites claires :
// tsconfig.json
{
  "files": [],
  "references": [
    { "path": "packages/types" },
    { "path": "packages/app" }
  ]
}
  • Dans chaque package, définissez "composite": true dans tsconfig.json pour prendre en charge les graphes de construction.
  • Suivez la couverture des types avec des outils tels que type-coverage et affichez les métriques dans l'intégration continue (CI).

Liste de contrôle de configuration de Copilot/Codeium pour VS Code

  • Verrouillez la version TypeScript de l’espace de travail afin que les suggestions de l’IA correspondent à vos fonctionnalités TypeScript.
  • Activer le filtre de code public de Copilot (politique au niveau de l'organisation) et restreindre la télémétrie.
  • Privilégiez la portée de l’espace de travail ; évitez d’envoyer des secrets/informations personnelles dans les invites.
// .vscode/settings.json
{
  // Pin workspace TypeScript
  "typescript.tsdk": "node_modules/typescript/lib",
  "typescript.enablePromptUseWorkspaceTsdk": true,

  // General hygiene
  "telemetry.telemetryLevel": "off",

  // GitHub Copilot (editor-side)
  "github.copilot.enable": {
    "*": true,
    "yaml": true,
    "markdown": true
  },
  // Note: Public code filter is configured at the org level (Copilot Business/Enterprise). Ensure it is enabled in your GitHub org settings.

  // Codeium
  // Keep suggestions on for TypeScript, prefer workspace context
  "codeium.enabledLanguages": {
    "typescript": true,
    "typescriptreact": true
  }
}

Modèle et restrictions de confidentialité

  • N'utilisez que des modèles approuvés : Configurez votre éditeur/plugin IA pour utiliser des fournisseurs/régions approuvés par l'organisation. Désactivez l'entraînement sur votre code et votre historique de chat.
  • Paramètres de la politique Copilot : Activer le filtre de code public, désactiver les suggestions de snippets de code qui correspondent étroitement à des sources publiques, et limiter la télémétrie selon la politique de l'organisation. Préférer la version TypeScript de l’espace de travail.
  • Listes blanches de dépôts : Limitez le contexte de l'IA à votre espace de travail et aux dépendances nécessaires. Évitez d'envoyer des informations personnelles, des secrets ou des schémas propriétaires dans les invites.
  • Limites de licence : Demandez à l'IA d'éviter le code littéral provenant de dépôts publics ; privilégiez des exemples idiomatiques et minimaux ainsi que des références à la documentation.

Pipelines de génération de code pour les types

Automatiser la génération de types afin que la dérive soit minimisée et que les suggestions d'IA soient alignées avec les schémas source de vérité.

  • OpenAPI → types TS : Utilisez openapi-typescript pour générer des types d'API.
{
  "scripts": {
    "codegen:openapi": "openapi-typescript openapi/schema.yaml -o src/types/api.d.ts"
  }
}
  • GraphQL → types TS : Utilisez @graphql-codegen/cli.
# codegen.yml
schema: schema.graphql
generates:
  src/types/graphql.ts:
    plugins:
      - typescript
      - typescript-operations
{
  "scripts": {
    "codegen:graphql": "graphql-codegen --config codegen.yml",
    "codegen": "npm run codegen:openapi && npm run codegen:graphql"
  }
}
  • Garde-fous CI : Exécutez la génération de code dans le CI et échouez si la différence générée n'est pas commitée.
# .github/workflows/codegen.yml
name: Types Codegen
on: [push, workflow_dispatch]
jobs:
  generate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run codegen
      - run: git diff --quiet || (git config user.name ci && git config user.email ci@example && git commit -am "chore(codegen): refresh types" && git push)

Bibliothèque de prompts

Utilisez des invites concises et directives pour orienter l'IA vers des résultats sûrs avec un typage strict.

  • « À partir de cet exemple JSON, dérivez des types TypeScript stricts, un schéma Zod, et une union discriminée exhaustive lorsque c’est applicable. »
  • « Analysez cette différence pour any/unknown et proposez des remplacements plus sûrs avec les restrictions et protections nécessaires. »
  • « Concevez une union discriminante pour ces variantes et refacturez ce switch pour qu’il soit exhaustif en utilisant un contrôle never. »
  • « Étant donné ce schéma OpenAPI, montrez des exemples de fetch typés et des types de requête/réponse de bout en bout. »
  • « Remplacez les assertions de type (as) par des gardes de type et un rétrécissement du flux de contrôle lorsque cela est possible. »
  • « Proposez un plan de mise à niveau de la rigueur de tsconfig (activer exactOptionalPropertyTypes, noUncheckedIndexedAccess) et les modifications minimales du code nécessaires. »
  • « Créez des validateurs Zod pour les gestionnaires d'API entrants et dérivez les types TypeScript avec z.infer. »
  • « Introduire des types de marque pour les identifiants (par exemple, UserId, OrderId) et migrer les usages de manière sécurisée. »
  • « Générez des tests qui assurent l’exhaustivité pour les types union via assertNever. »
  • « Suggérez des règles ESLint et des corrections automatiques pour prévenir les régressions vers any et les affectations non sécurisées. »

Liste de contrôle de la gouvernance

Conservez cette table dans votre dépôt (par exemple, docs/AI-GOVERNANCE.md) et réexaminez-la chaque trimestre.

ArticleDescriptionValeur d'exemple
Modèle/Fournisseur/VersionAssistant(s) approuvé(s) et version fixeCopilot Chat (OpenAI GPT-4o, révision 2025-06), Codeium Enterprise 1.8
Région/Point de terminaisonRégion d'hébergement et URL du point de terminaisonAzure EastUS, https://api.contoso.ai
Formation et rétentionQue les invites/le code soient utilisés pour la formation ou conservésFormation : désactivée ; Rétention : journaux masqués pendant 30 jours
Filtre de code publicBloquer les suggestions qui correspondent au code publicActivée
Repos/Projets autorisésChamp d'application où l'IA est autorisée
org/*-web, org/*-services
Autoriser/Refuser le contexteQuels fichiers/classes de données sont autorisés dans les invitesAutoriser : schémas, types ; Refuser : secrets, informations personnelles, contrats
Type de source de véritéD'où proviennent les types
openapi/schema.yaml, schema.graphql
Politique de génération de codeComment et quand régénérer les typesnpm run codegen sur PR + CI nocturne
Base de stricte typageOptions strictes minimales de tsconfig
strict, exactOptionalPropertyTypes, noUncheckedIndexedAccess
Référence de LintRègles requises pour prévenir les types dangereux@typescript-eslint/no-explicit-any, no-unsafe-* (erreur)
Garde-fous pour secrets/données personnellesScanners et rédactionAnalyse des secrets GitHub, rédaction avant validation
Télémétrie/JournalisationCe qui est enregistré par les outils d'IAErreurs seulement ; pas de fragments de code
Propriétaire de l'approbationQui approuve les modifications du modèle/configurationResponsable de la plateforme ingénierie
Dernière date de révisionDernier horodatage de révision de la politique2025-07-01

Manifeste de gouvernance YAML (plug-in)

Ajoutez ce fichier à /.well-known/ai-governance.yml (ou docs/ai-governance.yml) et maintenez-le en synchronisation avec le tableau ci-dessus.

version: 1
models:
  - name: copilot-chat
    provider: openai
    model: gpt-4o
    revision: "2025-06"
    region: azure-eastus
    endpoint: https://api.contoso.ai
    approved: true
    training_enabled: false
    retention_policy: masked-logs-30d
    public_code_filter: enabled
  - name: codeium-enterprise
    provider: codeium
    version: "1.8"
    approved: true
privacy:
  allow_context:
    - schemas
    - types
  deny_context:
    - secrets
    - pii
    - contracts
governance:
  approval_owner: "Engineering Platform Lead"
  last_review_date: "2025-07-01"
  next_review_due_days: 90
type_sources:
  - openapi/schema.yaml
  - schema.graphql
codegen:
  on_pr: true
  nightly_ci: true
tsconfig_baseline:
  strict: true
  exactOptionalPropertyTypes: true
  noUncheckedIndexedAccess: true
eslint_baseline:
  rules:
    "@typescript-eslint/no-explicit-any": "error"
    "@typescript-eslint/no-unsafe-assignment": "error"
    "@typescript-eslint/no-floating-promises": "error"
ci:
  checks:
    - typecheck
    - lint
telemetry:
  log_level: error
  include_code_snippets: false
security:
  secret_scanning: github
  precommit_redaction: enabled

Hook de pré-commit pour bloquer le code en attente de génération/validation

Utilisez soit Husky, soit un hook Git brut pour empêcher les commits lorsque npm run validate échoue ou que npm run codegen génère des différences non commises.

# .husky/pre-commit (Husky)
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npm run codegen
if ! git diff --quiet; then
  echo "codegen produced changes. Commit generated files before proceeding." >&2
  exit 1
fi

npm run validate
# .git/hooks/pre-commit (raw Git hook)
#!/usr/bin/env bash
set -euo pipefail

npm run codegen
if ! git diff --quiet; then
  echo "codegen produced changes. Commit generated files before proceeding." >&2
  exit 1
fi

npm run validate

Questions fréquemment posées

1. Interface vs type — quand devrais-je utiliser lequel ?

Utilisez interface pour les formes d'objet que vous prévoyez d'étendre (via extends) ou de fusionner (fusion de déclarations). Utilisez type pour les unions, les intersections, les types mappés, les types conditionnels et les types littéraux de template. Pour les formes d'objet simples, les deux fonctionnent—choisissez une convention et restez cohérent.

2. Quelle est la différence entre any, unknown et never ?

  • any : Désactive la sécurité des types ; à éviter en production.
  • inconnu: Type supérieur plus sûr ; doit être restreint avant utilisation.
  • never : Représente des valeurs qui ne se produisent jamais (fonctions qui lèvent une exception ou switches exhaustifs). Utile pour garantir l'exhaustivité.

3. Comment puis-je réduire les types en toute sécurité sans assertions as ?

Préférez les gardes de type et l'analyse du flux de contrôle :

function isString(value: unknown): value is string {
  return typeof value === 'string';
}

function upper(input: unknown) {
  if (isString(input)) return input.toUpperCase();
  if (typeof input === 'number') return String(input).toUpperCase();
  return '';
}

Utilisez également in/instanceof et des discriminants (comme les champs kind) pour affiner les unions.

4. Comment modéliser les variantes et imposer des switches exhaustifs ?

Utilisez une union discriminée et une vérification never :

type Shape = { kind: 'circle'; r: number } | { kind: 'rect'; w: number; h: number };

function area(s: Shape): number {
  switch (s.kind) {
    case 'circle':
      return Math.PI * s.r * s.r;
    case 'rect':
      return s.w * s.h;
    default: {
      const _exhaustive: never = s; // error if a new variant isn’t handled
      return _exhaustive;
    }
  }
}

5. Quelles options strictes de tsconfig comptent le plus ?

Activez strict et, au minimum : noImplicitAny, strictNullChecks, exactOptionalPropertyTypes et noUncheckedIndexedAccess. Combinez avec des règles ESLint comme @typescript-eslint/no-explicit-any et CI tsc --noEmit pour prévenir les régressions.

Conclusion

Créer vos propres types personnalisés pour représenter les structures de données utilisées dans votre propre code peut fournir une solution TypeScript flexible et utile pour votre projet. En plus d'augmenter la sécurité des types de votre propre code dans l'ensemble, avoir vos propres objets métier typés comme des structures de données dans le code augmentera la documentation globale de la base de code et améliorera votre propre expérience de développement lorsque vous travaillez avec des coéquipiers sur la même base de code.

De plus, l'intégration d'outils d'IA comme la plateforme AI Gradient™ de Linux-Console.net peut améliorer votre développement TypeScript en offrant des fonctionnalités de linting intelligent et d'auto-complétion, ce qui conduit à une meilleure gestion des types et à un processus de codage plus efficace.

Pour plus de tutoriels sur TypeScript, consultez notre page de la série Comment coder en TypeScript.

Continuez à apprendre : Tutoriels connexes sur Linux-Console.net

  • Comment utiliser les types de base en TypeScript : Apprenez à travailler avec des types fondamentaux tels que les nombres, les chaînes de caractères, les booléens, les tableaux et les tuples en TypeScript, en assurant une base solide pour vos projets.
  • Comment utiliser les interfaces dans TypeScript : Découvrez comment définir et étendre des interfaces pour structurer des objets, mettre en œuvre des contrats et assurer la sécurité des types dans votre code.
  • Comment utiliser les classes en TypeScript : Découvrez le monde des classes en TypeScript, y compris les champs de classe, les modificateurs d'accès, l'héritage et les classes abstraites, pour écrire un code plus structuré et facile à maintenir.
  • Comment utiliser les génériques en TypeScript : Maîtrisez l'art d'écrire des fonctions, classes et interfaces réutilisables et sûres en termes de type en utilisant des génériques, rendant votre code plus flexible et efficace.

Merci d'apprendre avec la communauté Linux-Console.net.