TypeScript în proiecte JavaScript (JSDoc)
Comentariile JSDoc îți permit să adaugi verificarea de tipuri TypeScript în fișiere JavaScript obișnuite, fără să le redenumești în .ts.
Este ideal atunci când vrei să migrezi un proiect pas cu pas sau când vrei pur și simplu siguranța tipurilor într-un cod JavaScript.
Notă: exemplele de pe această pagină sunt scrise ca JavaScript cu tipuri JSDoc. Salvează-le ca fișiere .js cu // @ts-check la început (sau cu checkJs activat) ca să vezi verificarea de tipuri descrisă. Într-un fișier .ts compilatorul ignoră tipurile JSDoc și se bazează în schimb pe adnotările obișnuite.
Primii pași
Ca TypeScript să îți verifice fișierele JavaScript trebuie:
- Să creezi un fișier
tsconfig.json(dacă nu ai deja unul) - Să activezi
checkJsîn el sau să pui// @ts-checkla începutul fiecărui fișier pe care vrei să îl verifici
Exemplu: JSDoc pentru siguranța tipurilor
Un comentariu JSDoc scris deasupra funcției îi spune lui TypeScript că atât parametrii, cât și valoarea returnată sunt numere.
// @ts-check activat la începutul fișierului.
Scrie o funcție add(a, b) documentată cu @param {number} a, @param {number} b și @returns {number}, care returnează suma. Este nevoie doar de declarație — programul nu afișează nimic.
Ieșire: program TypeScript și rezultatul capturat
// @ts-check
/**
* Adună două numere.
* @param {number} a
* @param {number} b
* @returns {number}
*/
function add(a, b) {
return a + b;
}
Obiecte și interfețe
Poți descrie forma unui obiect direct într-o etichetă JSDoc sau îi poți da un nume cu @typedef, astfel încât tipul să poată fi refolosit în mai multe locuri.
Tipuri de obiect inline
Tipul parametrului este scris pe loc, ca tip de obiect literal. Într-un fișier .js verificat, apelarea lui greet fără lastName este raportată ca eroare.
// @ts-check activat, documentează greet(person) cu @param {{ firstName: string, lastName: string, age?: number }} person și returnează Salut, <firstName> <lastName>. Apeleaz-o o dată cu un obiect complet (John Doe) și o dată doar cu firstName: "Jane", marcând al doilea apel cu un comentariu ca fiind eroarea de lastName lipsă pe care o raportează un fișier .js verificat. Nu se afișează nimic.
Ieșire: program TypeScript și rezultatul capturat
// @ts-check
/**
* @param {{ firstName: string, lastName: string, age?: number }} person
*/
function greet(person) {
return `Salut, ${person.firstName} ${person.lastName}`;
}
greet({ firstName: 'John', lastName: 'Doe' }); // OK
greet({ firstName: 'Jane' }); // Eroare: proprietatea 'lastName' lipsește
Folosirea lui @typedef pentru tipuri complexe
@typedef împreună cu liniile @property definesc un tip numit User. Parantezele drepte marchează o proprietate opțională, iar o uniune scrisă între paranteze rotunde limitează role la câteva valori.
@typedef și @property care descriu un tip de obiect în JSDoc.
Definește un typedef User cu id: number, username: string, email opțional, un role limitat la "admin" | "user" | "guest" și o metodă getFullName care returnează un șir de caractere. Declară currentUser cu /** @type {User} */ (id 1, username johndoe, role admin, getFullName() returnând John Doe) și afișează currentUser.role.
Ieșire: program TypeScript și rezultatul capturat
// @ts-check
/**
* @typedef {Object} User
* @property {number} id - ID-ul utilizatorului
* @property {string} username - Numele de utilizator
* @property {string} [email] - Adresa de e-mail (opțională)
* @property {('admin'|'user'|'guest')} role - Rolul utilizatorului
* @property {() => string} getFullName - Metodă care returnează numele complet
*/
/** @type {User} */
const currentUser = {
id: 1,
username: 'johndoe',
role: 'admin',
getFullName() {
return 'John Doe';
}
};
// TypeScript oferă autocompletare pentru proprietățile lui User
console.log(currentUser.role);
Extinderea tipurilor
O intersecție (Point & { z: number }) construiește un tip nou pornind de la unul existent. Într-un fișier .js verificat, obiectul fără z este o eroare, exact cea pe care o așteaptă comentariul @ts-expect-error. Compilat ca fișier .ts, tipurile JSDoc sunt ignorate, așa că în schimb compilatorul raportează directiva ca nefolosită.
Point ca { x: number, y: number } și Point3D ca Point & { z: number }. Declară point3d cu tipul Point3D, având x 1, y 2, z 3, apoi un obiect point2d fără z, precedat de un comentariu // @ts-expect-error - proprietatea z lipsește. Într-un fișier .js cu // @ts-check, lipsa lui z este eroarea așteptată; într-un fișier .ts tipurile JSDoc sunt ignorate, așa că în schimb compilatorul raportează directiva ca nefolosită.
Ieșire: program TypeScript și rezultatul capturat
// @ts-check
/** @typedef {{ x: number, y: number }} Point */
/**
* @typedef {Point & { z: number }} Point3D
*/
/** @type {Point3D} */
const point3d = { x: 1, y: 2, z: 3 };
// @ts-expect-error - proprietatea z lipsește
const point2d = { x: 1, y: 2 };
Tipuri de funcție
JSDoc poate descrie fiecare parte a unei funcții: parametrii ei, valoarea returnată, tipurile de callback și chiar mai multe semnături supraîncărcate.
Declarații de funcții
Fiecare @param dă unui parametru un tip și o scurtă descriere; @returns face același lucru pentru rezultat.
// @ts-check activat, scrie calculateArea(width, height) documentată cu @param {number} pentru ambii parametri (cu scurte descrieri) și @returns {number} Aria calculată, care returnează produsul. Pune calculateArea(10, 20) în area. Nu se afișează nimic.
Ieșire: program TypeScript și rezultatul capturat
// @ts-check
/**
* Calculează aria unui dreptunghi
* @param {number} width - Lățimea dreptunghiului
* @param {number} height - Înălțimea dreptunghiului
* @returns {number} Aria calculată
*/
function calculateArea(width, height) {
return width * height;
}
// TypeScript cunoaște tipurile parametrilor și al valorii returnate
const area = calculateArea(10, 20);
Expresii de funcție și callback-uri
@callback definește un tip de funcție reutilizabil. Acesta poate fi folosit apoi cu @type pe o funcție săgeată sau ca tip de parametru.
@callback pentru a tipiza expresii de funcție și parametri de tip callback în JSDoc.
Definește un tip de callback StringProcessor care primește un string și returnează un string. Declară toUpperCase cu @type {StringProcessor} ca funcție săgeată, apoi processStrings(strings, processor) documentată cu @param {string[]}, @param {StringProcessor} și @returns {string[]}, care trece tabloul prin processor folosind map. Apeleaz-o cu ["salut", "lume"] și toUpperCase. Nu se afișează nimic.
Ieșire: program TypeScript și rezultatul capturat
// @ts-check
/**
* @callback StringProcessor
* @param {string} input
* @returns {string}
*/
/**
* @type {StringProcessor}
*/
const toUpperCase = (str) => str.toUpperCase();
/**
* @param {string[]} strings
* @param {StringProcessor} processor
* @returns {string[]}
*/
function processStrings(strings, processor) {
return strings.map(processor);
}
const result = processStrings(['salut', 'lume'], toUpperCase);
// result va fi ['SALUT', 'LUME']
Supraîncărcarea funcțiilor
Mai multe blocuri @overload dau unei singure funcții mai multe semnături. Ultimul bloc descrie implementarea care le tratează pe toate.
@overload care dau unei singure funcții două semnături tipizate.
Scrie add(a, b) cu două blocuri @overload — string plus string returnează string, number plus number returnează number — urmate de semnătura de implementare string | number. Dacă oricare dintre argumente este un string, returnează concatenarea prin String(), altfel suma numerică. Apeleaz-o ca add("Salut, ", "Lume!") și add(10, 20). Nu se afișează nimic.
Ieșire: program TypeScript și rezultatul capturat
// @ts-check
/**
* @overload
* @param {string} a
* @param {string} b
* @returns {string}
*/
/**
* @overload
* @param {number} a
* @param {number} b
* @returns {number}
*/
/**
* @param {string | number} a
* @param {string | number} b
* @returns {string | number}
*/
function add(a, b) {
if (typeof a === 'string' || typeof b === 'string') {
return String(a) + String(b);
}
return a + b;
}
const strResult = add('Salut, ', 'Lume!'); // string
const numResult = add(10, 20); // number
Tipuri avansate
Funcționalități TypeScript mai avansate — uniuni, intersecții, generice, tipuri mapate și condiționale — sunt disponibile și prin JSDoc.
Tipuri uniune și intersecție
& combină tipuri, iar | permite unul dintre mai multe tipuri. O verificare cu in restrânge o uniune în interiorul unei funcții.
@typedef din JSDoc.
Definește Person (name, age), Employee ca Person & { employeeId: string } și Visitor ca Person | { guestId: string, visitDate: Date }. Declară un employee (Alice, 30, E123) și un guest (G456, new Date()), apoi getVisitorId(visitor), care folosește o verificare "guestId" in visitor pentru a returna fie guestId, fie name. Nu se afișează nimic.
Ieșire: program TypeScript și rezultatul capturat
// @ts-check
/** @typedef {{ name: string, age: number }} Person */
/** @typedef {Person & { employeeId: string }} Employee */
/** @typedef {Person | { guestId: string, visitDate: Date }} Visitor */
/** @type {Employee} */
const employee = {
name: 'Alice',
age: 30,
employeeId: 'E123'
};
/** @type {Visitor} */
const guest = {
guestId: 'G456',
visitDate: new Date()
};
/**
* @param {Visitor} visitor
* @returns {string}
*/
function getVisitorId(visitor) {
if ('guestId' in visitor) {
return visitor.guestId; // TypeScript știe că acesta este un oaspete
}
return visitor.name; // TypeScript știe că acesta este un Person
}
Tipuri mapate și condiționale
@template face un typedef generic. Corpul typedef-ului poate folosi apoi tipuri mapate, redenumirea cheilor și tipuri condiționale, exact ca într-un fișier .ts.
@template pot exprima tipuri mapate și condiționale.
Definește un typedef generic MethodNames<T> care păstrează doar cheile lui T ale căror valori sunt funcții, și un tip mapat generic Getters<T> care redenumește fiecare cheie K în `get${Capitalize<K>}`, returnând () => T[K]. Declară userGetters cu tipul Getters<{ name: string, age: number }>, unde getName returnează "John" și getAge returnează 30, și citește ambele valori în name și age. Adaugă export {} pentru ca name să nu intre în conflict cu variabila globală. Nu se afișează nimic.
Ieșire: program TypeScript și rezultatul capturat
// @ts-check
/**
* @template T
* @typedef {{ [K in keyof T]: T[K] extends Function ? K : never }[keyof T]} MethodNames
*/
/**
* @template T
* @typedef {{
* [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
* }} Getters
*/
/** @type {Getters<{ name: string, age: number }>} */
const userGetters = {
getName: () => 'John',
getAge: () => 30
};
// TypeScript impune tipurile returnate
const name = userGetters.getName(); // string
const age = userGetters.getAge(); // number
// Transformăm fișierul în modul, ca `name` să nu intre în conflict cu `name` global
export {};
Importul tipurilor
Un tip JSDoc poate fi importat din alt fișier sau dintr-un pachet cu import('...') în interiorul unui @typedef. Pentru bibliotecile care nu vin cu tipuri, îți poți scrie propriul fișier de declarații (.d.ts).
Importul tipurilor în JSDoc
Trei moduri de a aduce un tip într-un fișier JavaScript: dintr-un fișier TypeScript local, dintr-un pachet din node_modules și cu un nume nou.
// @ts-check
// Importing types from TypeScript files
/** @typedef {import('./types').User} User */
// Importing types from node_modules
/** @typedef {import('express').Request} ExpressRequest */
// Importing with renaming
/** @typedef {import('./api').default as ApiClient} ApiClient */
Fișier de declarații pentru un modul
Un fișier types.d.ts care declară pachetul my-module: o interfață Config și două funcții exportate.
// types.d.ts
declare module 'my-module' {
export interface Config {
apiKey: string;
timeout?: number;
retries?: number;
}
export function initialize(config: Config): void;
export function fetchData<T = any>(url: string): Promise<T>;
}
Bune practici
Urmează aceste practici când folosești JSDoc împreună cu TypeScript:
- Pune
// @ts-checkla începutul fiecărui fișier pe care vrei să îl verifici - Folosește
@typedefpentru tipurile complexe care apar în mai multe locuri - Documentează toți parametrii funcțiilor și tipurile returnate
- Folosește
@templatepentru funcții și tipuri generice - Scrie fișiere de declarații (
.d.ts) pentru bibliotecile externe fără tipuri - Preferă
@ts-expect-errorîn locul lui@ts-ignoreatunci când te aștepți la o eroare
Capcane frecvente
Ai grijă la aceste probleme frecvente:
- Lipsa lui
// @ts-check: fără el (sau fărăcheckJs) nu se verifică nimic - Sintaxă JSDoc incorectă: o singură greșeală de scriere poate opri verificarea de tipuri pentru acel comentariu
- Conflicte de tipuri: tipurile din surse diferite pot să nu se potrivească
- Probleme de inferență: uneori TypeScript nu poate deduce singur un tip
- Performanță: fișierele JavaScript mari, cu tipuri complexe, pot fi verificate încet
Concluzie
JSDoc împreună cu TypeScript este un mod puternic de a adăuga siguranța tipurilor într-un proiect JavaScript, fără a converti fișierele în TypeScript.
Este util în special pentru:
- Migrarea treptată a unui cod JavaScript către TypeScript
- Adăugarea verificării de tipuri într-un proiect JavaScript existent
- Lucrul în medii în care fișierele
.tsnu sunt acceptate - Documentarea codului JavaScript cu informații despre tipuri
Cu tiparele de pe această pagină obții multe dintre beneficiile TypeScript, continuând totuși să scrii JavaScript.
Ține minte: pentru proiecte noi sau pentru migrări complete, fișierele .ts oferă în continuare cea mai bună experiență TypeScript.
Ești gata să începi? Adaugă // @ts-check în fișierele tale JavaScript și începe să adaugi adnotări de tip JSDoc. Compilatorul va prinde erorile înainte ca ele să ajungă în producție.