Aide-mémoire sur les Types TypeScript avancés (avec des exemples)

Jun 15, 2020☕ ☕ 11 min Follow me on Twitter

Subscribe to receive the free weekly article

TypeScript est un langage typé qui vous permet de spécifier le type de variables, les paramètres de fonction, les valeurs renvoyées et les propriétés d'objet.

Voici un aide-mémoire sur les Types TypeScript avancés avec des exemples.

Les Types d'intersection

Un type d'intersection est un moyen de combiner plusieurs types en un seul. Cela signifie que vous pouvez fusionner un type donné A avec un type B ou plus et récupérer en retour un seul type avec toutes les propriétés fusionnées.

type LeftType = {
  id: number
  left: string
}

type RightType = {
  id: number
  right: string
}

type IntersectionType = LeftType & RightType

function showType(args: IntersectionType) {
  console.log(args)
}

showType({ id: 1, left: "test", right: "test" })
// Output: {id: 1, left: "test", right: "test"}

Comme vous pouvez le voir, IntersectionType combine deux types: LeftType et RightType et utilise le signe & pour construire le type d'intersection.

Les Types d'unions

Les types d'union vous permettent d'avoir des annotations de types différents dans une variable donnée.

type UnionType = string | number

function showType(arg: UnionType) {
  console.log(arg)
}

showType("test")
// Output: test

showType(7)
// Output: 7

La fonction showType est un type d'union qui accepte à la fois des chaînes et des nombres comme paramètre.

Les Types génériques

Un type générique est un moyen de réutiliser une partie d'un type donné. Il aide à capturer le type T transmis comme argument.

function showType<T>(args: T) {
  console.log(args)
}

showType("test")
// Output: "test"

showType(1)
// Output: 1

Pour construire un type générique, vous devez utiliser les crochets et passer T en paramètre. Ici, j'utilise T (le nom dépend de vous), puis j'appelle deux fois la fonction showType avec des annotations de types différents car elle est générique, elle peut être réutilisée.

interface GenericType<T> {
  id: number
  name: T
}

function showType(args: GenericType<string>) {
  console.log(args)
}

showType({ id: 1, name: "test" })
// Output: {id: 1, name: "test"}

function showTypeTwo(args: GenericType<number>) {
  console.log(args)
}

showTypeTwo({ id: 1, name: 4 })
// Output: {id: 1, name: 4}

Ici, nous avons un autre exemple qui a une interface GenericType qui reçoit un type générique T. Et comme il est réutilisable, nous pouvons l'appeler avec d'abord une chaîne, puis un numéro.

interface GenericType<T, U> {
  id: T
  name: U
}

function showType(args: GenericType<number, string>) {
  console.log(args)
}

showType({ id: 1, name: "test" })
// Output: {id: 1, name: "test"}

function showTypeTwo(args: GenericType<string, string[]>) {
  console.log(args)
}

showTypeTwo({ id: "001", name: ["This", "is", "a", "Test"] })
// Output: {id: "001", name: Array["This", "is", "a", "Test"]}

Un type générique peut recevoir plusieurs arguments. Ici, nous passons deux paramètres: T et U, puis nous les utilisons comme annotations de type pour les propriétés. Cela dit, nous pouvons maintenant utiliser l'interface et fournir différents types comme argument.

Les Types d'utilitaires

TypeScript fournit des utilitaires intégrés pratiques qui aident à manipuler facilement les types. Pour les utiliser, vous devez passer dans le <> le type que vous souhaitez transformer.

Partial

  • Partial<T>

Partial vous permet de rendre toutes les propriétés du type T facultatives. Il ajoutera une marque ? à côté de chaque champ.

interface PartialType {
  id: number
  firstName: string
  lastName: string
}

function showType(args: Partial<PartialType>) {
  console.log(args)
}

showType({ id: 1 })
// Output: {id: 1}

showType({ firstName: "John", lastName: "Doe" })
// Output: {firstName: "John", lastName: "Doe"}

Comme vous pouvez le voir, nous avons une interface PartialType qui est utilisée comme annotation de type pour les paramètres reçus par la fonction showType(). Et pour rendre les propriétés facultatives, nous devons utiliser le mot clé Partial et passer le type PartialType comme argument. Cela dit, tous les champs deviennent maintenant facultatifs.

Required

  • Required<T>

Contrairement à Partial, l'utilitaire Required rend toutes les propriétés du type T obligatoires.

interface RequiredType {
  id: number
  firstName?: string
  lastName?: string
}

function showType(args: Required<RequiredType>) {
  console.log(args)
}

showType({ id: 1, firstName: "John", lastName: "Doe" })
// Output: { id: 1, firstName: "John", lastName: "Doe" }

showType({ id: 1 })
// Error: Type '{ id: number: }' is missing the following properties from type 'Required<RequiredType>': firstName, lastName

L'utilitaire Required rendra toutes les propriétés requises même si nous les rendons facultatives avant d'utiliser l'utilitaire. Et si une propriété est omise, TypeScript enverra une erreur.

Readonly

  • Readonly<T>

Ce type d'utilitaire transformera toutes les propriétés du type T afin de les rendre non réaffectables avec une nouvelle valeur.

interface ReadonlyType {
  id: number
  name: string
}

function showType(args: Readonly<ReadonlyType>) {
  args.id = 4
  console.log(args)
}

showType({ id: 1, name: "Doe" })
// Error: Cannot assign to 'id' because it is a read-only property.

Ici, nous utilisons l'utilitaire Readonly pour rendre les propriétés de ReadonlyType non réaffectables. Cela dit, si vous essayez de donner une nouvelle valeur à l'un de ces champs, une erreur sera générée.

En plus de cela, vous pouvez également utiliser le mot clé readonly devant une propriété pour la rendre non réaffectable.

interface ReadonlyType {
  readonly id: number
  name: string
}

Pick

  • Pick<T, K>

Il vous permet de créer un nouveau type à partir d'un modèle existant T en sélectionnant certaines propriétés K de ce type.

interface PickType {
  id: number
  firstName: string
  lastName: string
}

function showType(args: Pick<PickType, "firstName" | "lastName">) {
  console.log(args)
}

showType({ firstName: "John", lastName: "Doe" })
// Output: {firstName: "John"}

showType({ id: 3 })
// Error: Object literal may only specify known properties, and 'id' does not exist in type 'Pick<PickType, "firstName" | "lastName">'

Pick est un peu différent des utilitaires précédents que nous avons déjà vus. Il attend deux paramètres: T est le type à partir duquel vous souhaitez sélectionner des éléments et K qui est la propriété que vous souhaitez sélectionner. Vous pouvez également sélectionner plusieurs champs en les séparant par un symbole de tuyau (|).

Omit

  • Omit<T, K>

L'utilitaire Omit est l'opposé du type Pick. Et au lieu de sélectionner des éléments, il supprimera les propriétés K du type T.

interface PickType {
  id: number
  firstName: string
  lastName: string
}

function showType(args: Omit<PickType, "firstName" | "lastName">) {
  console.log(args)
}

showType({ id: 7 })
// Output: {id: 7}

showType({ firstName: "John" })
// Error: Object literal may only specify known properties, and 'firstName' does not exist in type 'Pick<PickType, "id">'

Cet utilitaire est similaire à la façon dont Pick fonctionne. Il attend le type et les propriétés à omettre de ce type.

Extract

  • Extract<T, U>

Extract vous permet de construire un type en choisissant des propriétés présentes dans deux types différents. L'utilitaire extraira de T toutes les propriétés attribuables à U.

interface FirstType {
  id: number
  firstName: string
  lastName: string
}

interface SecondType {
  id: number
  address: string
  city: string
}

type ExtractType = Extract<keyof FirstType, keyof SecondType>
// Output: "id"

Ici, nous avons deux types qui ont en commun la propriété id. Et donc en utilisant le mot-clé Extract, nous récupérons le champ id car il est présent dans les deux interfaces. Et si vous avez plusieurs champs partagés, l'utilitaire extraira toutes les propriétés similaires.

Exclude

Contrairement à Extract, l'utilitaire Exclude construira un type en excluant les propriétés qui sont déjà présentes dans deux types différents. Il exclut de T tous les champs attribuables à U.

interface FirstType {
  id: number
  firstName: string
  lastName: string
}

interface SecondType {
  id: number
  address: string
  city: string
}

type ExcludeType = Exclude<keyof FirstType, keyof SecondType>

// Output; "firstName" | "lastName"

Comme vous pouvez le voir ici, les propriétés firstName et lastName sont assignables au type SecondType car elles n'y sont pas présentes. Et en utilisant le mot-clé Extract, nous récupérons ces champs comme prévu.

Record

  • Record<K, T>

Cet utilitaire vous aide à construire un type avec un ensemble de propriétés K d'un type donné T. Record est vraiment pratique quand il s'agit de mapper les propriétés d'un type à un autre.

interface EmployeeType {
  id: number
  fullname: string
  role: string
}

let employees: Record<number, EmployeeType> = {
  0: { id: 1, fullname: "John Doe", role: "Designer" },
  1: { id: 2, fullname: "Ibrahima Fall", role: "Developer" },
  2: { id: 3, fullname: "Sara Duckson", role: "Developer" },
}

// 0: { id: 1, fullname: "John Doe", role: "Designer" },
// 1: { id: 2, fullname: "Ibrahima Fall", role: "Developer" },
// 2: { id: 3, fullname: "Sara Duckson", role: "Developer" }

Le fonctionnement de Record est relativement simple. Ici, il attend un nombre comme type, c'est pourquoi nous avons 0, 1 et 2 comme clés pour la variable employees. Et si vous essayez d'utiliser une chaîne comme propriété, une erreur sera génerée. Ensuite, l'ensemble des propriétés est donné par EmployeeType d'où l'objet avec les champs id, fullName et role.

NonNullable

  • NonNullable<T>

Il vous permet de supprimer null et undefined du type T.

type NonNullableType = string | number | null | undefined

function showType(args: NonNullable<NonNullableType>) {
  console.log(args)
}

showType("test")
// Output: "test"

showType(1)
// Output: 1

showType(null)
// Error: Argument of type 'null' is not assignable to parameter of type 'string | number'.

showType(undefined)
// Error: Argument of type 'undefined' is not assignable to parameter of type 'string | number'.

Ici, nous passons le type NonNullableType comme argument à l'utilitaire NonNullable qui construit un nouveau type en excluant null et undefined de ce type. Cela dit, si vous passez une valeur nulle, TypeScript enverra une erreur.

Par ailleurs, si vous ajoutez l'indicateur --strictNullChecks au fichier tsconfig, TypeScript appliquera des règles de non-nullabilité.

Les Types mappés

Les types mappés vous permettent de prendre un modèle existant et de transformer chacune de ses propriétés en un nouveau type. Notez que certains types d'utilitaires couverts précédemment sont également des types mappés.

type StringMap<T> = {
  [P in keyof T]: string
}

function showType(arg: StringMap<{ id: number; name: string }>) {
  console.log(arg)
}

showType({ id: 1, name: "Test" })
// Error: Type 'number' is not assignable to type 'string'.

showType({ id: "testId", name: "This is a Test" })
// Output: {id: "testId", name: "This is a Test"}

StringMap<> transformera tous les types transmis en chaîne. Cela dit, si nous l'utilisons dans la fonction showType(), les paramètres reçus doivent être une chaîne, sinon, une erreur sera générée par TypeScript.

Les Gardes de type

Les gardes de type vous permettent de vérifier le type d'une variable ou d'un objet avec un opérateur. Il s'agit d'un bloc conditionnel qui renvoie un type en utilisant typeof, instanceof ou in.

  • typeof
function showType(x: number | string) {
  if (typeof x === "number") {
    return `The result is ${x + x}`
  }
  throw new Error(`This operation can't be done on a ${typeof x}`)
}

showType("I'm not a number")
// Error: This operation can't be done on a string

showType(7)
// Output: The result is 14

Comme vous pouvez le voir, nous avons un bloc conditionnel JavaScript normal qui vérifie le type de l'argument reçu avec typeof. Avec cela en place, vous pouvez maintenant protéger votre type avec cette condition.

  • instanceof
class Foo {
  bar() {
    return "Hello World"
  }
}

class Bar {
  baz = "123"
}

function showType(arg: Foo | Bar) {
  if (arg instanceof Foo) {
    console.log(arg.bar())
    return arg.bar()
  }

  throw new Error("The type is not supported")
}

showType(new Foo())
// Output: Hello World

showType(new Bar())
// Error: The type is not supported

Comme l'exemple précédent, celui-ci est également un garde de type qui vérifie si le paramètre reçu fait partie de la classe Foo ou non et le gère en conséquence.

  • in
interface FirstType {
  x: number
}
interface SecondType {
  y: string
}

function showType(arg: FirstType | SecondType) {
  if ("x" in arg) {
    console.log(`The property ${arg.x} exists`)
    return `The property ${arg.x} exists`
  }
  throw new Error("This type is not expected")
}

showType({ x: 7 })
// Output: The property 7 exists

showType({ y: "ccc" })
// Error: This type is not expected

L'opérateur in vous permet de vérifier si la propriété x existe ou non sur l'objet reçu en paramètre.

Les Types conditionnels

Il teste deux types et en sélectionne un en fonction du résultat de ce test.

type NonNullable<T> = T extends null | undefined ? never : T

Cet exemple de type d'utilitaire NonNullable vérifie si le type est nul ou non et le gère en fonction de cela. Et comme vous pouvez le constater, il utilise l'opérateur ternaire JavaScript.

Merci d'avoir lu.

#typescript

Support my work

Get articles in your inbox