TypeScript pro JavaScript vývojáře: rychlý a praktický start
Typy, rozhraní, union typy, generika a tsconfig vysvětlené pro ty, kdo umí JavaScript. Jak postupně převést existující projekt na TypeScript.
Pokud umíte JavaScript, umíte z velké části i TypeScript. TypeScript je JavaScript s typovými anotacemi, které se při kompilaci odstraní – za běhu nic nepřidávají. Jejich hodnota je v tom, že chyby jako undefined is not a function najde editor dřív, než kód spustíte.
Instalace a první kompilace
npm install --save-dev typescript
npx tsc --init
Vznikne tsconfig.json. Soubor index.ts zkompilujete příkazem npx tsc, kontrolu typů bez generování souborů spustíte přes npx tsc --noEmit. V projektech s Vite, Next.js apod. kompilaci řeší bundler a tsc --noEmit používáte jen na kontrolu (např. v CI).
Odvozování typů: nepište víc, než musíte
let count = 0; // number – odvozeno
const name = 'Petr'; // typ 'Petr' (literál)
const items = [1, 2]; // number[]
count = 'deset'; // ❌ Type 'string' is not assignable to type 'number'
Anotace pište hlavně u parametrů funkcí a veřejných API. Návratový typ si TypeScript obvykle odvodí sám.
function formatPrice(amount: number, currency = 'CZK'): string {
return new Intl.NumberFormat('cs-CZ', { style: 'currency', currency }).format(amount);
}
Objekty: type a interface
interface User {
id: number;
email: string;
name?: string; // nepovinné
readonly createdAt: Date; // nelze přepsat
}
type Role = 'admin' | 'editor' | 'viewer';
type UserWithRole = User & { role: Role };
Rozdíl je v praxi malý. interface lze rozšiřovat přes extends a slučovat deklarace, type umí union typy a složitější kombinace. Rozumná konvence: interface pro tvary objektů, type pro všechno ostatní. Hlavně buďte konzistentní.
Union typy a zužování
Union typ říká „jedno z těchto”. TypeScript pak hlídá, že jste ošetřili všechny možnosti:
function printId(id: number | string) {
if (typeof id === 'string') {
console.log(id.toUpperCase()); // zde je id: string
} else {
console.log(id.toFixed(0)); // zde je id: number
}
}
Nejsilnější vzor je diskriminovaný union – stav, který nemůže být nekonzistentní:
type RequestState<T> =
| { status: 'loading' }
| { status: 'error'; error: string }
| { status: 'success'; data: T };
function render(state: RequestState<User[]>) {
switch (state.status) {
case 'loading': return 'Načítám…';
case 'error': return `Chyba: ${state.error}`;
case 'success': return `${state.data.length} uživatelů`;
}
}
Nemůže nastat situace „loading je true a zároveň máme chybu”, kterou známe z tří samostatných proměnných.
null a undefined
Se zapnutým strict (doporučeno) vás TypeScript donutí ošetřit chybějící hodnoty:
const el = document.querySelector('#app'); // Element | null
el.innerHTML = 'Ahoj'; // ❌ 'el' is possibly 'null'
if (el) el.innerHTML = 'Ahoj'; // ✅
el?.classList.add('ready'); // ✅
Operátor ! (el!.innerHTML) kontrolu vypne. Používejte ho jen, když si jste opravdu jistí – je to slib kompilátoru, ne kontrola za běhu.
Generika
Generikum je typový parametr – funkce funguje pro libovolný typ a zachová ho:
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
const n = first([1, 2, 3]); // number | undefined
const s = first(['a', 'b']); // string | undefined
Praktický příklad – typovaný fetch:
async function getJson<T>(url: string): Promise<T> {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json() as Promise<T>;
}
const users = await getJson<User[]>('/api/users');
Pozor: as je jen tvrzení. TypeScript nekontroluje, co server skutečně poslal. Pro data zvenčí (API, formuláře, localStorage) použijte validační knihovnu jako Zod, která typ ověří za běhu a zároveň ho odvodí.
Užitečné vestavěné typy
| Typ | Co dělá |
|---|---|
Partial<User> |
všechny vlastnosti nepovinné |
Required<User> |
všechny povinné |
Pick<User, 'id' \| 'email'> |
jen vybrané vlastnosti |
Omit<User, 'createdAt'> |
bez vybraných vlastností |
Record<string, number> |
objekt se string klíči a number hodnotami |
ReturnType<typeof fn> |
návratový typ funkce |
function updateUser(id: number, changes: Partial<Omit<User, 'id'>>) { /* … */ }
any vs. unknown
any vypíná typovou kontrolu – a šíří se dál kódem. unknown říká „nevím, co to je” a před použitím vás donutí typ ověřit:
try {
riskyOperation();
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err);
console.error(msg);
}
Rozumný tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"noUncheckedIndexedAccess": true,
"skipLibCheck": true,
"esModuleInterop": true,
"noEmit": true
},
"include": ["src"]
}
noUncheckedIndexedAccess přidá | undefined k přístupu přes index (arr[5], obj[key]), což odhalí reálné chyby. moduleResolution: "Bundler" je vhodné pro projekty s Vite nebo podobným bundlerem; pro čistý Node.js použijte "NodeNext" (spolu s "module": "NodeNext").
Postupná migrace existujícího JS projektu
- Do
tsconfig.jsonpřidejte"allowJs": truea"checkJs": false. - Přejmenovávejte soubory
.js→.tsjeden po druhém, začněte utilitami bez závislostí. - Pro knihovny bez typů instalujte
@types/nazev-balicku(např.npm i -D @types/lodash). - Až to půjde, zapněte
strict: true. Pokud je chyb moc, zapínejte dílčí volby (strictNullChecksatd.) postupně.
Alternativa bez přejmenování: JSDoc komentáře a // @ts-check na začátku .js souboru. Editor pak kontroluje typy i v čistém JavaScriptu.
Při migraci velkého projektu pomůže nahrát ho jako ZIP do projektu v ColdHost AI a nechat si navrhnout typy pro jednotlivé moduly – úpravy pak zkontrolujete přes tsc --noEmit a vrátíte do repozitáře jako pull request.