Typescript
Senior
Quiz-Typescript-Senior
Typescript-Decorators
Mapped-Types
Utility-Types
Partial
Omit
Questions-Entretien-Typescript-Senior
Qcm-Typescript-Avance
Infer
Patterns-Ts
Architecture-Ts
Back-End
📝
Entretien TypeScript Senior — Decorators et Utility Types
20 questions avancées TypeScript : decorators, mapped types, utility types (Partial, Omit, ReturnType), mot-clé infer, architecture et patterns TypeScript. Idéal pour un entretien Senior.
Banque de révision : les 30 questions corrigées
Voici l'intégralité des 30 questions de « Entretien TypeScript Senior — Decorators et Utility Types », chacune avec sa bonne réponse et son explication. Le quiz interactif ci-dessus en tire une sélection aléatoire à chaque tentative — utilisez cette liste complète pour réviser.
Explication :
TypeScript infère le type le plus spécifique possible. Pour les objets et tableaux,
as const force l'inférence vers des types littéraux en lecture seule. Pour les fonctions génériques, on peut contraindre l'inférence avec des paramètres génériques et des contraintes (extends).
Exemple :
const config = { name: "test" } as const → type { readonly name: "test" } au lieu de { name: string }.
Explication :
Les interfaces supportent la "declaration merging" : si on déclare deux fois la même interface, elles fusionnent automatiquement. Les types génèrent des erreurs de duplication. De plus, les interfaces sont généralement plus rapides à compiler car elles sont simples à mettre en cache.
Utilisez
interface pour les APIs publiques et les objets qui pourraient être étendus. Utilisez type pour les unions, intersections, et types complexes.
Explication :
TypeScript infère
this du contexte où la fonction est appelée. Pour typer explicitement this, on peut utiliser un premier paramètre fictif nommé this avec son type. Ce paramètre disparaît à la compilation.
Exemple :
function add(this: { x: number }, y: number) { return this.x + y; }
Explication :
Un prédicat de type est une fonction de garde (type guard) qui utilise la syntaxe
valeur is Type comme type de retour. Elle permet à TypeScript de restreindre le type d'une variable dans la branche conditionnelle.
Exemple :
function isString(value: unknown): value is string { return typeof value === "string"; }
Explication :
TypeScript peut inférer les types des paramètres de callback à partir de la signature attendue. Le piège classique : un paramètre de callback marqué optionnel peut recevoir
undefined, ce que le code peut ne pas gérer.
Exemple problématique :
arr.forEach((item, index?) => { ... }) - index peut être undefined.
Explication :
L'analyse de flot de contrôle permet à TypeScript de comprendre comment les types évoluent à travers les branches conditionnelles, les boucles, et les retours de fonction. Par exemple, après
if (typeof x === "string"), TypeScript sait que x est une string.
Cette analyse fonctionne avec les gardes de type (
typeof, instanceof, in, et les prédicats personnalisés).
Explication :
TypeScript a un système de types structurel (ou "duck typing"). Deux types sont compatibles si leur structure correspond, même s'ils ont des noms différents. Par exemple,
interface Person { name: string } est compatible avec { name: string }.
Pour créer une compatibilité nominale (basée sur le nom), on peut utiliser des "brands" :
type UserId = string & { __brand: "userId" }.
Explication :
La variance définit comment les sous-typage se comporte pour les types composés. En TypeScript : les paramètres de fonction sont contravariants (on peut passer un type plus général), le retour est covariant (on peut retourner un type plus spécifique). Les tableaux et les promesses sont covariants par défaut.
Attention : la covariance des tableaux peut causer des erreurs :
const nombres: number[] = [1,2]; const valeurs: (number|string)[] = nombres; valeurs.push("hello"); est autorisé mais dangereux.
Explication :
Les types conditionnels utilisent la syntaxe
T extends U ? Vrai : Faux. Ils sont évalués statiquement. Cas d'usage : type NonNullable<T> = T extends null | undefined ? never : T; ou ReturnType<T>.
Les types conditionnels peuvent être distribués sur les unions :
T extends U ? X : Y appliqué à une union évalue la condition pour chaque membre.
Explication :
Le mot-clé
infer dans un type conditionnel permet de "capturer" un type à inférer. Exemple classique : type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never; capture le type de retour de la fonction.
On peut utiliser
infer plusieurs fois : T extends [infer First, ...infer Rest] pour extraire le premier élément d'un tuple.
Explication :
En TypeScript, les clés d'un type peuvent être de type
string, number, ou symbol (pour les objets avec des clés calculées). keyof T & string est une intersection qui filtre pour ne garder que les clés qui sont assignables à string.
Utilisez
keyof T & string quand vous avez besoin de garantir que les clés sont des chaînes (par exemple pour Record<string, ...>).
Explication :
Les mapped types itèrent sur les clés pour créer un nouveau type. On peut ajouter/enlever
readonly (readonly ou -readonly) et l'optionnalité (? ou -?). Exemple : type Partial<T> = { [P in keyof T]?: T[P] }.
Les modificateurs
+ sont implicites. Utilisez - pour supprimer : type Required<T> = { [P in keyof T]-?: T[P] }.
Explication :
Les template literal types utilisent la même syntaxe que les template strings JavaScript mais au niveau des types. Exemple :
type EventName = `on${Capitalize}` génère toutes les chaînes commençant par "on" suivies d'une lettre majuscule.
Combine avec
infer pour parser des chaînes : type GetPath<T> = T extends `/${infer Rest}` ? Rest : never;
Explication :
Une union discriminée a une propriété commune (souvent
type ou kind) dont la valeur littérale permet d'identifier le membre exact. TypeScript utilise cette propriété dans les switch ou if pour affiner le type.
Exemple :
type Shape = { kind: "circle"; radius: number } | { kind: "square"; size: number }
Explication :
La "module augmentation" permet d'ajouter des membres à des interfaces ou modules existants. On utilise
declare module "module" { interface T { ... } }. Exemple typique : ajouter une méthode à l'interface Array ou étendre les types d'Express.
Utile pour typer des bibliothèques JavaScript sans types, ou pour ajouter des méthodes globales à
Window.
Explication :
Dans les méthodes de classe,
this est typé comme l'instance de la classe. Mais si on passe la méthode comme callback, this peut être perdu. Solutions : fonctions fléchées (capturent le this lexical) ou bind.
Activez
noImplicitThis dans tsconfig.json pour détecter les pertes potentielles de this.
Explication :
object : tout type qui n'est pas primitif (string, number, boolean, symbol, null, undefined). Object : tout type avec les méthodes de Object.prototype. {} : tout type sauf null et undefined. Record<string, unknown> : un objet avec des clés string et des valeurs inconnues.
Préférez
object ou Record<string, unknown> selon le besoin. Évitez {} et Object qui sont trop permissifs.
Explication :
Une annotation de type (
let x: string = valeur) force TypeScript à vérifier que valeur est assignable à string. Une assertion (valeur as string) contourne cette vérification. L'assertion peut masquer des erreurs et doit être utilisée avec précaution.
Utilisez d'abord une annotation de type. Si l'annotation échoue, revoyez votre logique. L'assertion est un dernier recours.
Explication :
Sans
exactOptionalPropertyTypes, TypeScript permet d'assigner undefined à une propriété optionnelle (ex: { age?: number } peut recevoir age: undefined). Avec l'option activée, il faut explicitement age?: number | undefined pour autoriser undefined.
Cette option rend le typage plus précis et évite la confusion entre "propriété absente" et "propriété présente avec undefined".
Explication :
En TSX (TypeScript + JSX),
<T> au début d'une expression peut être interprété comme une balise JSX. Pour éviter cela, on utilise <T extends {}> ou on ajoute une virgule <T, >. C'est une particularité de la syntaxe TSX.
La syntaxe
const f = <T, >(x: T): T => x; est également valide et plus explicite.
Explication :
TypeScript efface tous les types à la compilation. Contrairement à Java où les génériques sont effacés mais les types de base restent, TypeScript ne conserve aucune information de type à l'exécution. C'est pourquoi
instanceof ne fonctionne pas avec les interfaces ou les types génériques.
Pour vérifier un type à l'exécution, utilisez les "type guards" (
typeof, instanceof, ou propriétés discriminantes).
Explication :
Les décorateurs TypeScript sont une fonctionnalité expérimentale (flag
experimentalDecorators). Ils permettent d'annoter et de modifier les classes et leurs membres. Ils ne font pas encore partie de la spécification ECMAScript finale (proposal stage 3).
Angular utilise intensément les décorateurs. Pour du code générique, préférez d'autres patterns car la syntaxe peut changer.
Explication :
L'opérateur
satisfies permet de vérifier qu'une valeur est compatible avec un type sans étendre le type de la valeur. Exemple : const colors = { red: "#FF0000" } satisfies Record<string, string>; garde l'inférence précise ("red" au lieu de string) tout en vérifiant le type.
Utile pour les objets de configuration où vous voulez garder les types littéraux tout en validant la structure.
Explication :
as const appliqué à un objet ou tableau le rend readonly et infère les types littéraux les plus précis. Exemple : const couleurs = ["rouge", "vert"] as const devient readonly ["rouge", "vert"] au lieu de string[].
Très utile pour créer des unions discriminées ou des configurations avec des valeurs exactes.
Explication :
Par défaut, TypeScript utilise le structural typing. Pour simuler du nominal typing (où le nom du type compte), on utilise des "brands" (propriétés fantômes). Exemple :
type UserId = string & { readonly __brand: unique symbol }.
Les brands sont utiles pour éviter les confusions entre des types structurellement identiques (ex: UserId vs ProductId).
Explication :
Les types récursifs s'auto-référencent, comme
type JSON = string | number | boolean | null | JSON[] | { [key: string]: JSON }. TypeScript impose une limite de profondeur (configurable) pour éviter les boucles infinies.
Pour les structures profondément récursives, utilisez des interfaces plutôt que des types pour de meilleures performances.
Explication :
Les "assertion functions" utilisent
asserts condition ou asserts value is Type. Si la condition est fausse, la fonction lance une erreur. TypeScript utilise ces assertions pour affiner les types après l'appel.
Exemple :
function assertIsString(value: unknown): asserts value is string { if (typeof value !== "string") throw new Error(); }
Explication :
Node resolution est le mode par défaut, qui suit les règles de Node.js (cherche dans node_modules). Classic est un ancien mode. L'option
paths permet de créer des alias : "paths": { "@app/*": ["./src/app/*"] }.
Pour les chemins personnalisés, assurez-vous que votre bundler (Webpack, Vite) est configuré de la même manière.
Explication :
export type (et import type) indique que l'export/import concerne uniquement un type, pas une valeur. Cela permet au compilateur de les effacer complètement, ce qui peut casser des cycles d'import circulaires et améliorer les performances.
Utilisez
import type quand vous n'avez besoin que du type, pas de la valeur à l'exécution.
Explication :
Les temps de compilation peuvent devenir longs sur de gros projets. Stratégies :
--skipLibCheck (ignore les librairies), --incremental (recompile uniquement ce qui a changé), project references (découpe en projets), et éviter les types génériques trop complexes qui explosent combinatoriquement.
Utilisez
tsc --generateTrace pour analyser les goulets d'étranglement de compilation.