Fermer

novembre 29, 2022

Comment comprendre facilement les génériques TypeScript


Dans cet article, nous décomposons les concepts derrière les génériques TypeScript et expliquons l’avantage de les utiliser pour créer une réutilisabilité dans notre code TypeScript.

Les génériques TypeScript sont un élément de TypeScript qui déroute souvent les nouveaux arrivants car il rend le code TypeScript beaucoup plus compliqué qu’il ne l’est en réalité.

Tout d’abord, cela aide à comprendre ce que sont les génériques. Generics est une méthode/un outil de programmation qui existe dans des langages comme C# et Java et est conçu pour aider à créer des composants réutilisables qui peuvent fonctionner avec une variété de types différents. Les génériques rendent cela possible en permettant l’abstraction des types utilisés dans les fonctions ou les variables. TypeScript adopte ce modèle en nous permettant de créer du code qui peut fonctionner avec différents types.

Nous allons passer par un exemple simple pour illustrer les bases des génériques. Supposons que nous ayons une fonction appelée init() qui a reçu un argument et a renvoyé ledit argument.

const init = (arg) => {
  return arg;
};

Manuscrit

Pour ceux qui ne connaissent pas TypeScript, le point fort de TypeScript est la possibilité de spécifier des types pour nos variables, paramètres, fonctions, etc. dans notre code. Cela aide à rendre notre code JavaScript plus sûr en s’assurant que les types sont vérifiés pendant temps de compilation (c’est-à-dire lorsque le code est en cours de compilation) par opposition à Durée (c’est-à-dire lorsque le code est en cours d’exécution).

S’il y a un problème ou une incompatibilité dans nos types, TypeScript nous aidera à nous en informer avant que nous n’ayons à exécuter notre code.

Depuis notre init() fonction ci-dessus est censée renvoyer ce qu’elle reçoit, nous pouvons spécifier que le type de l’argument et la valeur renvoyée par la fonction sont identiques. A titre d’exemple, on peut dire qu’ils auraient le type number.

const init = (arg: number): number => {
  return arg;
};

Si nous devions fournir une valeur qui n’est pas un number à notre fonction, TypeScript émettra une erreur.

const init = (arg: number): number => {
  return arg;
};

init(5); 
init("5"); 

Si nous avons essayé de changer le type de retour de la fonction, TypeScript affichera un avertissement, et à juste titre, car il déduit le type du paramètre renvoyé par la fonction.

const init = (arg: number): string => {
  return arg; 
};

Et si on voulait le init() fonction réutilisable pour différents types? Une chose que nous pourrions essayer de faire est de tirer parti Union Types où le type d’argument et le type renvoyé peuvent être l’un des nombreux types.

Par exemple, nous pourrions dire que l’argument de la fonction peut accepter un number ou un string et renvoyer soit un number ou un string.

const init = (arg: number | string): number | string => {
  return arg;
};

init(5); 
init("5"); 

Bien que cela fonctionne dans certains cas, l’exemple ci-dessus ne sera pas réutilisable dans les cas où nous ne connaissons pas le type d’argument que nous allons transmettre.

Une autre approche que nous pourrions adopter et qui fonctionnerait pour tous les types consiste à utiliser le any taper.

const init = (arg: any): any => {
  return arg;
};

init(5); 
init("5"); 

Utilisant any fonctionnerait, mais c’est loin d’être idéal puisque nous ne pourrons pas contraindre les arguments que la fonction accepte ou déduire ce que la fonction doit retourner. Ceci est dû au fait any est un type TypeScript spécial dont le type n’est pas vérifié, il ne doit donc être utilisé qu’avec parcimonie.

Génériques TypeScript

C’est là qu’interviennent les génériques et la capacité de transmettre une variable de type. Tout comme nous l’avons dit init() peut accepter un argument, on peut aussi dire que init() peut accepter un variable de typeou en d’autres termes un paramètre de type ou un argument de type.

Dans TypeScript, nous pouvons passer des variables de type avec la syntaxe des chevrons (<>). Voici un exemple d’avoir le init() fonction accepte une variable de type désignée par la lettre T.

const init = <T>(arg: any): any => {
  return arg;
};

Tout comme la façon dont l’argument de valeur est disponible dans la fonction, le taper L’argument est également disponible dans la fonction. Nous pourrions dire que quelle que soit la variable de type transmise, ce sera le type de l’argument et le type de retour de la fonction.

const init = <T>(arg: T): T => {
  return arg;
};

Pour réitérer ce qui se passe ci-dessus :

  • Notre init() la fonction accepte une variable de type désignée par la lettre T.
  • Nous attribuons la valeur de ce type à la valeur de type du arg paramètre: arg: T.
  • Enfin, nous indiquons que le type de retour de la fonction est également la même variable de type transmise à notre fonction : const init = (): T => {}

Si nous devions maintenant réutiliser le init() fonction à des fins différentes, nous pouvons dicter la variable de type dans la fonction pour différents paramètres.

const init = <T>(arg: T): T => {
  return arg;
};

init<number>(5); 
init<string>("5"); 
init<any>({ fresh: "kicks" }); 

Dans l’exemple ci-dessus, TypeScript sera suffisamment intelligent pour reconnaître la valeur de la variable de type Tsans toujours spécifier une valeur de type (par exemple, <any>). Cela ne fonctionne que dans des cas simples. Dans les cas plus compliqués, nous devrons nous assurer que les variables de type sont transmises.

Interfaces génériques

Les génériques peuvent être largement utilisés et ne sont pas seulement spécifiques aux fonctions. Supposons que nous voulions avoir notre init() fonction créer un objet qui a un field propriété d’une valeur de arg.

const init = <T>(arg: T): T => {
  const obj = {
    field: arg,
  };
  return arg;
};

De plus, nous pouvons supposer que nous voulions contraindre le type obj créé pour un type d’interface particulier. Les alias et les interfaces de type TypeScript acceptent également les variables de type.

Les interfaces et les alias de type nous permettent de décrire les types de nos objets JavaScript.


type InitObj = {};


interface InitObj {}

Voici un exemple d’affectation d’un type d’interface à un objet qui contient un string propriété.

interface InitObj {
  field: string;
}

const obj: InitObj = {
  field: "hello world", 
};

Pour décrire la forme des objets, un alias de type ou une interface peut être utilisé avec différences mineures entre eux. Nous utiliserons une interface puisque l’équipe TypeScript a toujours utilisé des interfaces pour décrire la forme des objets.

Nous pourrions créer un InitObj interface au-dessus de la fonction qui définit le type d’un field propriété à une variable de type transmise.

interface InitObj<T> {
  field: T;
}

const init = <T>(arg: T): T => {
  const obj = {
    field: arg,
  };
  return arg;
};

Dans le init() fonction, nous pouvons définir le type de obj comme le InitObj interface et passez la variable de type. Nous pouvons également faire en sorte que la fonction renvoie la propriété du champ à partir de obj pour se conformer au type de retour attendu du init() fonction.

interface InitObj<T> {
  field: T;
}

const init = <T>(arg: T): T => {
  const obj: InitObj<T> = {
    field: arg,
  };
  return obj.field;
};

Valeurs génériques par défaut

TypeScript permet également d’avoir des valeurs de type génériques par défaut (c’est-à-dire, valeurs par défaut des paramètres génériques). Voici un exemple d’avoir notre init() fonction et InitObj interface assigne une valeur de type par défaut de any à la variable de type qui peut être transmise.

interface InitObj<T = any> {
  field: T;
}

const init = <T = any>(arg: T): T => {
  const obj: InitObj<T> = {
    field: arg,
  };
  return obj.field;
};

Maintenant, si une variable de type n’est pas définie lors de l’utilisation de la init() fonction et le compilateur n’est pas en mesure de déduire ce que pourrait être la variable de type, elle sera simplement définie sur any.

Variables de type multiple

Par convention, la lettre T est souvent utilisé pour déduire une variable de type, probablement en raison du fait qu’il représente Type. Nous pourrions très bien utiliser n’importe quelle lettre que nous voulons…U, Vetc.

Dans certains cas, certains préfèrent extrapoler le nom de la variable de type, surtout si l’on peut passer plusieurs variables de type. Voici un exemple de init() fonction pouvant accepter deux variables de type—TData et TVariables.

interface InitObj<T = any> {
  field: T;
}

const init = <TData = any, TVariables = any>(arg: TData): TData => {
  const obj: InitObj<TData> = {
    field: arg,
  };
  return obj.field;
};

Pour résumer ce que nous avons appris, décomposons étape par étape l’exemple de code ci-dessus.

En dehors de notre fonction, nous créons une interface pour décrire la forme d’un objet qui a une field propriété.

  • L’interface accepte une variable de type notée T et donné une valeur par défaut de any.
  • L’interface attribue à la variable de type le type du field propriété.
interface InitObj<T = any> {
  field: T;
}

Nous déclarons une init() fonction.

  • La fonction accepte deux variables de type—TData et TVariables. Les deux reçoivent une valeur par défaut de any. Comme nous n’utilisions pas le TVariables interface dans notre fonction, nous allons donc l’omettre de notre fonction.
  • Nous attribuons le TData variable de type comme étant le type de arg paramètre et la valeur retournée de la fonction.
const init = <TData = any>(arg: TData): TData => {
  
};

Dans notre init() fonction, nous créons un objet avec le nom de obj qui a un field propriété. La field la propriété reçoit une valeur de arg paramètre passé dans la fonction.

  • Nous précisons le type de obj être InitObj. Comme nous spécifions son type, nous passons le TData comme variable de type à la InitObj.
const init = <TData = any>(arg: TData): TData => {
  const obj: InitObj<TData> = {
    field: arg,
  };
};

Enfin, notre fonction renvoie simplement la valeur de obj.field propriété.

interface InitObj<T = any> {
  field: T;
}

const init = <TData = any>(arg: TData): TData => {
  const obj: InitObj<TData> = {
    field: arg,
  };
  return obj.field;
};

init<number>(5); 
init<string>("5"); 


init<string>({ nestedField: "5" });


init<{ nestedField: string }>({ nestedField: "5" });

Conclusion

Les génériques sont un outil puissant pour aider à rendre le code TypeScript réutilisable tout en garantissant la sécurité du type. Pour les personnes qui découvrent TypeScript, la syntaxe des crochets angulaires peut être l’une des premières choses qui rend le code TypeScript difficile à lire. Nous espérons que cet article vous aidera à expliquer, de manière simple et compréhensible, comment les génériques peuvent être implémentés dans TypeScript.




Source link

novembre 29, 2022