Introduction : Pourquoi ne pouvez-vous pas simplement modifier le type ?
\n\nImaginez la situation : vous intégrez une bibliothèque puissante pour travailler avec des graphiques. Elle a une documentation parfaite, des centaines d'étoiles sur GitHub, et même des fichiers .d.ts avec les descriptions de types. Mais il y a un hic — le développeur de la bibliothèque a décidé que le champ data devait être string | number, alors que vous avez besoin strictement de Date. Vous allez dans node_modules, modifiez le fichier — et tout se casse au prochain npm install.
Ça vous parle ? C'est l'un des problèmes les plus fréquents rencontrés par les développeurs TypeScript. La question « Overriding interface property type defined in Typescript d.ts file » a rassemblé des milliers de vues sur Stack Overflow. Et ce n'est pas pour rien — le diable se cache dans les détails.
\n\nDans cet article, nous allons examiner 5 méthodes légales pour remplacer le type d'une propriété d'interface depuis un fichier .d.ts externe. Pas de piratage avec copie de fichiers — que du TypeScript pur.
Comprendre le problème : pourquoi TypeScript résiste-t-il ?
\n\nAvant de contourner le système, comprenons sa logique. TypeScript est strict avec les types non par méchanceté, mais pour votre sécurité. Lorsque vous essayez de remplacer une propriété d'interface, vous déclarez en fait : « Je sais mieux que l'auteur de la bibliothèque ». Et TypeScript demande : « Tu es sûr ? Prouve-le ».
\n\nPrenons un exemple classique. Supposons que nous ayons une interface provenant d'une bibliothèque :
\n\n// node_modules/some-library/types.d.ts\nexport interface UserConfig {\n id: string | number;\n name: string;\n createdAt: Date | string;\n metadata: Record<string, unknown>;\n}\n\nVous voulez que createdAt soit uniquement de type Date. Un simple héritage ne fonctionnera pas — TypeScript générera une erreur si vous essayez de réduire le type. Mais il existe des solutions de contournement.
Méthode 1 : Declaration Merging (fusion de déclarations)
\n\nLa méthode la plus élégante, quand elle est possible. Si la bibliothèque exporte une interface et non un type (type), vous pouvez la « compléter » dans la portée globale ou dans un module.
Condition importante : les interfaces portant le même nom dans la même portée sont automatiquement fusionnées. Mais on ne peut pas remplacer une propriété existante — seulement en ajouter une nouvelle.
\n\nCette méthode convient donc lorsqu'il faut étendre l'interface, et non modifier un champ existant. Par exemple :
\n\n// global.d.ts\nexport interface UserConfig {\n customField: boolean; // ✅ Ajout d'un nouveau champ\n}\n\n// Mais ceci génère une erreur :\nexport interface UserConfig {\n createdAt: Date; // ❌ Erreur : identifiant en double\n}\n\nConclusion : Declaration Merging est un excellent outil pour ajouter des champs, mais inutile pour remplacer des champs existants.
\n\nMéthode 2 : Utilisation de Omit et d'une interface personnalisée
\n\nC'est la solution miracle pour 90% des cas. Le principe est simple : on « supprime » la propriété problématique via Omit et on crée un nouveau type avec le type souhaité.
// Votre code\ntype MyUserConfig = Omit<UserConfig, 'createdAt'> & {\n createdAt: Date; // ✅ Maintenant uniquement Date\n};\n\n// Utilisation\nconst config: MyUserConfig = {\n id: 42,\n name: 'Alice',\n createdAt: new Date('2024-01-01'), // Fonctionne\n metadata: { role: 'admin' }\n};\n\nQue se passe-t-il ici ? Omit<UserConfig, 'createdAt'> crée un type sans le champ createdAt. Ensuite, via l'intersection de types (&), on rajoute ce champ, mais avec le type souhaité.
Avantages : idéal pour des modifications ponctuelles, n'affecte pas l'interface d'origine.
\nInconvénients : si plusieurs propriétés sont concernées, le code devient lourd.
Méthode 3 : Extension via Pick et Partial
\n\nIl arrive qu'il faille remplacer non pas une, mais plusieurs propriétés. Ou vous voulez rendre tous les champs optionnels,