Овладейте TypeScript декларационни файловете (.d.ts), за да отключите безопасността на типовете и автоматичното довършване за всяка JavaScript библиотека. Научете се да използвате @types, да създавате свои собствени дефиниции и да обработвате код от трети страни като професионалист.
Отключване на JavaScript екосистемата: Задълбочен преглед на TypeScript декларационните файлове
TypeScript революционизира съвременната уеб разработка, като донесе статично типизиране в динамичния свят на JavaScript. Тази безопасност на типовете предоставя невероятни предимства: улавяне на грешки по време на компилация, даване на възможност за мощно автоматично довършване в редактора и правене на големи кодови бази значително по-лесни за поддръжка. Въпреки това, основно предизвикателство възниква, когато искаме да използваме огромната екосистема от съществуващи JavaScript библиотеки - повечето от които не са написани на TypeScript. Как нашият стриктно типизиран TypeScript код разбира формите, функциите и променливите от нетипизирана JavaScript библиотека?
Отговорът се крие в TypeScript декларационните файлове. Тези файлове, идентифицируеми по тяхното .d.ts разширение, са основният мост между световете на TypeScript и JavaScript. Те действат като план или API договор, описващ типовете на библиотека на трета страна, без да съдържат нейното действително изпълнение. В този изчерпателен наръчник ще проучим всичко, което трябва да знаете, за да управлявате уверено дефиниции на типове за всяка JavaScript библиотека във вашите TypeScript проекти.
Какво представляват TypeScript декларационните файлове?
Представете си, че сте наели изпълнител, който говори само на различен език. За да работите ефективно с него, ще ви е необходим преводач или подробен набор от инструкции на език, който и двамата разбирате. Декларационният файл служи за тази цел за TypeScript компилатора (изпълнителя).
.d.ts файлът съдържа само информация за типа. Той включва:
- Подписи за функции и методи (типове параметри, типове връщане).
- Дефиниции за променливи и техните типове.
- Интерфейси и типови псевдоними за сложни обекти.
- Дефиниции на класове, включително техните свойства и методи.
- Пространствени имена и модулни структури.
Най-важното е, че тези файлове не съдържат изпълним код. Те са изцяло за статичен анализ. Когато импортирате JavaScript библиотека като Lodash във вашия TypeScript проект, компилаторът търси съответстващ декларационен файл. Ако намери такъв, той може да валидира вашия код, да предостави интелигентно автоматично довършване и да гарантира, че използвате библиотеката правилно. Ако не намери такъв, ще генерира грешка като: Could not find a declaration file for module 'lodash'.
Защо декларационните файлове са незаменими за професионалното развитие
Използването на JavaScript библиотеки без подходящи дефиниции на типове в TypeScript проект подкопава самата причина за използването на TypeScript на първо място. Нека разгледаме прост сценарий, използващ популярната помощна библиотека, Lodash.
Светът без дефиниции на типове
Без декларационен файл, TypeScript няма представа какво е lodash или какво съдържа. За да накарате кода да се компилира, може да се изкушите да използвате бърза корекция като тази:
const _: any = require('lodash');
const users = [{ 'user': 'barney' }, { 'user': 'fred' }];
// Autocomplete? No help here.
// Type checking? No. Is 'username' the correct property?
// The compiler allows this, but it might fail at runtime.
_.find(users, { username: 'fred' });
В този случай, променливата _ е от тип any. Това ефективно казва на TypeScript, "Не проверявайте нищо свързано с тази променлива." Губите всички предимства: няма автоматично довършване, няма проверка на типовете на аргументите и няма сигурност относно типа връщане. Това е развъдник на грешки по време на изпълнение.
Светът с дефиниции на типове
Сега, нека да видим какво се случва, когато предоставим необходимия декларационен файл. След инсталиране на типовете (което ще разгледаме по-нататък), опитът се трансформира:
import _ from 'lodash';
interface User {
user: string;
active?: boolean;
}
const users: User[] = [{ 'user': 'barney' }, { 'user': 'fred' }];
// 1. Editor provides autocompletion for 'find' and other lodash functions.
// 2. Hovering over 'find' shows its full signature and documentation.
// 3. TypeScript sees that `users` is an array of `User` objects.
// 4. TypeScript knows the predicate for `find` on `User[]` should involve `user` or `active`.
// CORRECT: TypeScript is happy.
const fred = _.find(users, { user: 'fred' });
// ERROR: TypeScript catches the mistake!
// Property 'username' does not exist on type 'User'.
const betty = _.find(users, { username: 'betty' });
Разликата е огромна. Получаваме пълна безопасност на типовете, превъзходно разработчиково изживяване чрез инструменти и драстично намаляване на потенциалните грешки. Това е професионалният стандарт за работа с TypeScript.
Йерархията на намиране на дефиниции на типове
И така, как получавате тези магически .d.ts файлове за любимите си библиотеки? Има добре установен процес, който обхваща огромното мнозинство от сценариите.
Стъпка 1: Проверете дали библиотеката пакетира свои собствени типове
Най-добрият сценарий е, когато дадена библиотека е написана на TypeScript или нейните поддържащи предоставят официални декларационни файлове в същия пакет. Това става все по-често за съвременни, добре поддържани проекти.
Как да проверите:
- Инсталирайте библиотеката както обикновено:
npm install axios - Погледнете вътре в папката на библиотеката в
node_modules/axios. Виждате ли някакви.d.tsфайлове? - Проверете
package.jsonфайла на библиотеката за"types"или"typings"поле. Това поле сочи директно към основния декларационен файл. Например,package.jsonна Axios съдържа:"types": "index.d.ts".
Ако тези условия са изпълнени, сте готови! TypeScript автоматично ще намери и използва тези пакетирани типове. Не са необходими допълнителни действия.
Стъпка 2: Проектът DefinitelyTyped (@types)
За хилядите JavaScript библиотеки, които не пакетират свои собствени типове, глобалната TypeScript общност е създала невероятен ресурс: DefinitelyTyped.
DefinitelyTyped е централизирано, управлявано от общността хранилище в GitHub, което хоства висококачествени декларационни файлове за огромен брой JavaScript пакети. Тези дефиниции се публикуват в npm регистъра под обхвата @types.
Как да го използвате:
Ако библиотека като lodash не пакетира свои собствени типове, просто инсталирате съответния @types пакет като зависимост за разработка:
npm install --save-dev @types/lodash
Конвенцията за именуване е проста и предвидима: за пакет, наречен package-name, неговите типове почти винаги ще бъдат на @types/package-name. Можете да търсите налични типове на уебсайта на npm или директно в хранилището на DefinitelyTyped.
Защо --save-dev? Декларационните файлове са необходими само по време на разработка и компилация. Те не съдържат никакъв код за изпълнение, така че не трябва да бъдат включени във вашия финален производствен пакет. Инсталирането им като devDependency гарантира това разделение.
Стъпка 3: Когато няма типове - Писане на ваши собствени
Какво ще стане, ако използвате по-стара, нишова или вътрешна частна библиотека, която не пакетира типове и не е в DefinitelyTyped? В този случай трябва да запретнете ръкави и да създадете свой собствен декларационен файл. Въпреки че това може да звучи плашещо, можете да започнете просто и да добавяте повече подробности, ако е необходимо.
Бързата поправка: Стенографска декларация на околен модул
Понякога просто трябва да накарате проекта си да се компилира без грешки, докато измислите подходяща стратегия за типизиране. Можете да създадете файл във вашия проект (напр., declarations.d.ts или types/global.d.ts) и да добавите стенографска декларация:
// in a .d.ts file
declare module 'some-untyped-library';
Това казва на TypeScript, "Вярвайте ми, съществува модул, наречен 'some-untyped-library'. Просто третирайте всичко, импортирано от него като тип any." Това заглушава грешката на компилатора, но както обсъдихме, той жертва цялата безопасност на типовете за тази библиотека. Това е временен кръпка, а не дългосрочно решение.
Създаване на основен потребителски декларационен файл
По-добър подход е да започнете да дефинирате типовете за частите от библиотеката, които действително използвате. Да кажем, че имаме проста библиотека, наречена `string-utils`, която експортира една функция.
// In node_modules/string-utils/index.js
module.exports.capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);
Можем да създадем string-utils.d.ts файл в специална директория `types` в корена на нашия проект.
// In my-project/types/string-utils.d.ts
declare module 'string-utils' {
export function capitalize(str: string): string;
// You could add other function definitions here as you use them
// export function slugify(str: string): string;
}
Сега трябва да кажем на TypeScript къде да намери нашите потребителски дефиниции на типове. Правим това в tsconfig.json:
{
"compilerOptions": {
// ... other options
"baseUrl": ".",
"paths": {
"*": ["types/*"]
}
}
}
С тази настройка, когато import { capitalize } from 'string-utils', TypeScript ще намери вашия потребителски декларационен файл и ще осигури безопасността на типовете, които сте дефинирали. Можете постепенно да изградите този файл, докато използвате повече функции на библиотеката.
Гмурване по-дълбоко: Създаване на декларационни файлове
Нека да проучим някои по-усъвършенствани концепции, които ще срещнете, когато пишете или четете декларационни файлове.
Деклариране на различни видове експорти
JavaScript модулите могат да експортират неща по различни начини. Вашият декларационен файл трябва да съответства на експортната структура на библиотеката.
- Именувани експорти: Това е най-често срещаното. Видяхме го по-горе с `export function capitalize(...)`. Можете също да експортирате константи, интерфейси и класове.
- Експорт по подразбиране: За библиотеки, които използват `export default`.
- UMD глобални променливи: За по-стари библиотеки, предназначени да работят в браузъри чрез таг
<script>, те често се прикачват към глобалния обект `window`. Можете да декларирате тези глобални променливи. - `export =` and `import = require()`: Този синтаксис е за по-стари CommonJS модули, които използват `module.exports = ...`. Например, ако дадена библиотека прави `module.exports = myClass;`.
declare module 'my-lib' {
export const version: string;
export interface Options { retries: number; }
export function doSomething(options: Options): Promise
declare module 'my-default-lib' {
// For a function default export
export default function myCoolFunction(): void;
// For an object default export
// const myLib = { name: 'lib', version: '1.0' };
// export default myLib;
}
// Declares a global variable '$' of a certain type
declare var $: JQueryStatic;
// in my-class.d.ts
declare class MyClass { constructor(name: string); }
export = MyClass;
// in your app.ts
import MyClass = require('my-class');
const instance = new MyClass('test');
Въпреки че е по-рядко срещан със съвременните ES модули, това е от решаващо значение за съвместимост с много по-стари, но все още широко използвани Node.js пакети.
Разширяване на модули: Разширяване на съществуващи типове
Една от най-мощните функции е разширяването на модули (известно още като сливане на декларации). Това ви позволява да добавяте свойства към съществуващи интерфейси, дефинирани в декларационния файл на друг пакет. Това е изключително полезно за библиотеки с архитектура на плъгини, като Express или Fastify.
Представете си, че използвате middleware в Express, което добавя свойство `user` към обекта `Request`. Без разширяване, TypeScript ще се оплаче, че `user` не съществува в `Request`.
Ето как можете да кажете на TypeScript за това ново свойство:
// in your types/express.d.ts file
// We must import the original type to augment it
import { UserProfile } from './auth'; // Assuming you have a UserProfile type
// Tell TypeScript we're augmenting the 'express-serve-static-core' module
declare module 'express-serve-static-core' {
// Target the 'Request' interface inside that module
interface Request {
// Add our custom property
user?: UserProfile;
}
}
Сега, в цялото ви приложение, обектът Express `Request` ще бъде правилно типизиран с незадължителното свойство `user` и ще получите пълна безопасност на типовете и автоматично довършване.
Директиви с три наклонени черти
Понякога може да видите коментари в горната част на .d.ts файлове, които започват с три наклонени черти (///). Това са директиви с три наклонени черти, които действат като инструкции за компилатора.
/// <reference types="..." />: Това е най-често срещаното. Той изрично включва дефинициите на типове на друг пакет като зависимост. Например, типовете за плъгин на WebdriverIO може да включват/// <reference types="webdriverio" />, защото собствените му типове зависят от основните типове на WebdriverIO./// <reference path="..." />: Това се използва за деклариране на зависимост от друг файл в същия проект. Това е по-стар синтаксис, до голяма степен заменен от ES модулни импорти.
Най-добри практики за управление на декларационни файлове
- Предпочитайте пакетирани типове: Когато избирате между библиотеки, предпочитайте тези, които са написани на TypeScript или пакетират свои собствени официални дефиниции на типове. Това сигнализира за ангажимент към TypeScript екосистемата.
- Съхранявайте
@typesвdevDependencies: Винаги инсталирайте пакетите@typesс--save-devили-D. Те не са необходими за вашия производствен код. - Подравнете версиите: Чест източник на грешки е несъответствие между версията на библиотеката и нейната
@typesверсия. Основното повишаване на версията в библиотека (напр. от v2 на v3) вероятно ще има промени, нарушаващи съвместимостта в нейния API, което трябва да бъде отразено в пакета@types. Опитайте се да ги поддържате синхронизирани. - Използвайте
tsconfig.jsonза контрол: Опциите на компилатораtypeRootsиtypesвъв вашияtsconfig.jsonмогат да ви дадат фин контрол върху това къде TypeScript търси декларационни файлове.typeRootsказва на компилатора кои папки да провери (по подразбиране е./node_modules/@types), аtypesви позволява изрично да изброите кои типови пакети да включите. - Допринесете обратно: Ако напишете изчерпателен декларационен файл за библиотека, която няма такъв, помислете дали да не го допринесете към проекта DefinitelyTyped. Това е фантастичен начин да върнете на глобалната общност от разработчици и да помогнете на хиляди други.
Заключение: Неопетите герои на безопасността на типовете
TypeScript декларационните файлове са неопетите герои, които правят възможно безпроблемното интегриране на динамичния, разрастващ се свят на JavaScript в стабилна, типово безопасна среда за разработка. Те са критичната връзка, която дава възможност на нашите инструменти, предотвратява безброй грешки и прави нашите кодови бази по-устойчиви и самодокументиращи се.
Като разберете как да намирате, използвате и дори да създавате свои собствени .d.ts файлове, вие не просто отстранявате грешка на компилатора - вие повишавате целия си работен процес на разработка. Отключвате пълния потенциал както на TypeScript, така и на богатата екосистема от JavaScript библиотеки, създавайки мощна синергия, която води до по-добър, по-надежден софтуер за глобална аудитория.