TypeScript - Kompletny przewodnik po języku, który zmienił JavaScript
Czym jest TypeScript i dlaczego stał się standardem?
TypeScript to język programowania stworzony przez Microsoft, który jest typowanym nadzbiorem JavaScript. Każdy kod JavaScript jest poprawnym kodem TypeScript, ale TypeScript dodaje opcjonalne statyczne typowanie, które pozwala wykrywać błędy w czasie kompilacji zamiast w runtime.
W 2025 roku TypeScript jest używany przez większość profesjonalnych projektów JavaScript, w tym Angular, Vue 3, Next.js, Prisma i tysiące innych bibliotek i aplikacji.
Dlaczego TypeScript?
Wykrywanie błędów przed uruchomieniem
// JavaScript - błąd w runtime
function greet(name) {
return 'Hello, ' + name.toUppercase() // typo - toUpperCase
}
greet('world') // Error w runtime!
// TypeScript - błąd w edytorze
function greet(name: string): string {
return 'Hello, ' + name.toUppercase() // ❌ Error: Property 'toUppercase' does not exist
}Autouzupełnianie i dokumentacja
interface User {
id: number
name: string
email: string
role: 'admin' | 'user'
}
function getUser(id: number): User {
// ...
}
const user = getUser(1)
user. // Autouzupełnianie: id, name, email, roleRefaktoryzacja bez strachu
TypeScript sprawdza cały codebase i wskazuje wszystkie miejsca wymagające zmiany przy refaktoryzacji.
Podstawy typów
Typy prymitywne
// Podstawowe typy
let name: string = 'Jan'
let age: number = 25
let isActive: boolean = true
let nothing: null = null
let notDefined: undefined = undefined
// Inferencja typów - TypeScript sam wyznacza typ
let city = 'Warszawa' // typ: string
let count = 42 // typ: number
// Tablice
let numbers: number[] = [1, 2, 3]
let names: Array<string> = ['Anna', 'Bartek']
// Tuple - tablica o stałej długości i typach
let person: [string, number] = ['Jan', 25]
// Enum
enum Status {
Pending = 'pending',
Active = 'active',
Completed = 'completed'
}
let taskStatus: Status = Status.ActiveTypy specjalne
// any - wyłącza type checking (unikaj!)
let anything: any = 'string'
anything = 42 // OK, ale niebezpieczne
// unknown - bezpieczniejsza alternatywa dla any
let value: unknown = 'hello'
// value.toUpperCase() // ❌ Error - musi być sprawdzony
if (typeof value === 'string') {
value.toUpperCase() // ✅ OK
}
// never - funkcja nigdy nie kończy się normalnie
function throwError(message: string): never {
throw new Error(message)
}
function infiniteLoop(): never {
while (true) {}
}
// void - funkcja nic nie zwraca
function logMessage(message: string): void {
console.log(message)
}Union i Intersection Types
// Union - jeden z typów
type StringOrNumber = string | number
function printId(id: StringOrNumber) {
if (typeof id === 'string') {
console.log(id.toUpperCase())
} else {
console.log(id.toFixed(2))
}
}
// Literal types
type Direction = 'north' | 'south' | 'east' | 'west'
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'
// Intersection - kombinacja typów
interface HasName {
name: string
}
interface HasAge {
age: number
}
type Person = HasName & HasAge
const person: Person = {
name: 'Jan',
age: 25
}Interfejsy i Type Aliases
Interfejsy
// Podstawowy interface
interface User {
id: number
name: string
email: string
}
// Opcjonalne pola
interface Config {
apiUrl: string
timeout?: number // opcjonalne
retry?: boolean
}
// Readonly
interface Point {
readonly x: number
readonly y: number
}
const p: Point = { x: 10, y: 20 }
// p.x = 5 // ❌ Error - readonly
// Rozszerzanie interfejsów
interface Animal {
name: string
}
interface Dog extends Animal {
breed: string
bark(): void
}
// Interfejsy dla funkcji
interface SearchFunction {
(query: string, limit?: number): Promise<Result[]>
}
const search: SearchFunction = async (query, limit = 10) => {
// ...
}
// Interfejsy dla klas
interface Serializable {
serialize(): string
deserialize(data: string): void
}
class User implements Serializable {
serialize(): string {
return JSON.stringify(this)
}
deserialize(data: string): void {
Object.assign(this, JSON.parse(data))
}
}Type Aliases
// Type alias - alternatywa dla interface
type User = {
id: number
name: string
}
// Type alias dla prymitywów
type ID = string | number
type Nullable<T> = T | null
// Type alias dla funkcji
type EventHandler = (event: Event) => void
// Type alias dla unii
type Result<T> =
| { success: true; data: T }
| { success: false; error: string }
function fetchData(): Result<User[]> {
try {
const data = fetch('/api/users')
return { success: true, data }
} catch (err) {
return { success: false, error: 'Failed to fetch' }
}
}Interface vs Type - kiedy co?
// Interface - dla obiektów, można rozszerzać
interface Animal {
name: string
}
interface Dog extends Animal {
breed: string
}
// Interfejsy można "mergować"
interface User {
name: string
}
interface User {
age: number
}
// User ma teraz name i age
// Type - dla unii, tuple, prymitywów
type Status = 'pending' | 'active' | 'done'
type Coordinates = [number, number]
type Callback = () => void
// Type nie można rozszerzać (ale można intersection)
type Cat = Animal & { meow(): void }Generyki
Podstawy generyków
// Funkcja generyczna
function identity<T>(value: T): T {
return value
}
const num = identity<number>(42) // typ: number
const str = identity('hello') // typ: string (inferencja)
// Generyczna tablica
function first<T>(array: T[]): T | undefined {
return array[0]
}
const firstNumber = first([1, 2, 3]) // typ: number | undefined
// Generyczny interface
interface Box<T> {
value: T
}
const stringBox: Box<string> = { value: 'hello' }
const numberBox: Box<number> = { value: 42 }Ograniczenia generyków (Constraints)
// Constraint - T musi mieć właściwość length
function getLength<T extends { length: number }>(item: T): number {
return item.length
}
getLength('hello') // ✅ OK
getLength([1, 2, 3]) // ✅ OK
getLength({ length: 10 }) // ✅ OK
// getLength(42) // ❌ Error - number nie ma length
// keyof constraint
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
const user = { name: 'Jan', age: 25 }
const name = getProperty(user, 'name') // typ: string
const age = getProperty(user, 'age') // typ: number
// getProperty(user, 'invalid') // ❌ ErrorGeneryki z wartościami domyślnymi
interface ApiResponse<T = unknown> {
data: T
status: number
message?: string
}
// Bez podania typu - T = unknown
const response: ApiResponse = { data: 'test', status: 200 }
// Z podaniem typu
const userResponse: ApiResponse<User> = {
data: { id: 1, name: 'Jan' },
status: 200
}Generyki w klasach
class Queue<T> {
private items: T[] = []
enqueue(item: T): void {
this.items.push(item)
}
dequeue(): T | undefined {
return this.items.shift()
}
peek(): T | undefined {
return this.items[0]
}
get length(): number {
return this.items.length
}
}
const numberQueue = new Queue<number>()
numberQueue.enqueue(1)
numberQueue.enqueue(2)
const first = numberQueue.dequeue() // typ: number | undefinedUtility Types
TypeScript dostarcza wbudowane typy pomocnicze:
interface User {
id: number
name: string
email: string
age?: number
}
// Partial - wszystkie pola opcjonalne
type PartialUser = Partial<User>
// { id?: number; name?: string; email?: string; age?: number }
// Required - wszystkie pola wymagane
type RequiredUser = Required<User>
// { id: number; name: string; email: string; age: number }
// Readonly - wszystkie pola tylko do odczytu
type ReadonlyUser = Readonly<User>
// Pick - wybierz tylko niektóre pola
type UserName = Pick<User, 'id' | 'name'>
// { id: number; name: string }
// Omit - pomiń niektóre pola
type UserWithoutEmail = Omit<User, 'email'>
// { id: number; name: string; age?: number }
// Record - słownik
type UserRoles = Record<string, 'admin' | 'user' | 'guest'>
const roles: UserRoles = {
'jan@example.com': 'admin',
'anna@example.com': 'user'
}
// Extract - wybierz z unii
type Numbers = Extract<string | number | boolean, number>
// number
// Exclude - wyklucz z unii
type StringOrBoolean = Exclude<string | number | boolean, number>
// string | boolean
// NonNullable - usuń null i undefined
type MaybeString = string | null | undefined
type DefinitelyString = NonNullable<MaybeString>
// string
// ReturnType - typ zwracany przez funkcję
function createUser() {
return { id: 1, name: 'Jan' }
}
type UserType = ReturnType<typeof createUser>
// { id: number; name: string }
// Parameters - typy parametrów funkcji
function greet(name: string, age: number) {}
type GreetParams = Parameters<typeof greet>
// [string, number]Type Guards
// typeof guard
function processValue(value: string | number) {
if (typeof value === 'string') {
return value.toUpperCase()
}
return value.toFixed(2)
}
// instanceof guard
class Dog {
bark() { console.log('Woof!') }
}
class Cat {
meow() { console.log('Meow!') }
}
function makeSound(animal: Dog | Cat) {
if (animal instanceof Dog) {
animal.bark()
} else {
animal.meow()
}
}
// in guard
interface Bird {
fly(): void
layEggs(): void
}
interface Fish {
swim(): void
layEggs(): void
}
function move(animal: Bird | Fish) {
if ('fly' in animal) {
animal.fly()
} else {
animal.swim()
}
}
// Custom type guard
interface Admin {
role: 'admin'
permissions: string[]
}
interface User {
role: 'user'
email: string
}
function isAdmin(person: Admin | User): person is Admin {
return person.role === 'admin'
}
function getAccess(person: Admin | User) {
if (isAdmin(person)) {
return person.permissions // TypeScript wie że to Admin
}
return ['read'] // TypeScript wie że to User
}Zaawansowane typy
Mapped Types
// Tworzenie nowych typów przez mapowanie
type Readonly<T> = {
readonly [P in keyof T]: T[P]
}
type Optional<T> = {
[P in keyof T]?: T[P]
}
type Nullable<T> = {
[P in keyof T]: T[P] | null
}
// Przykład użycia
interface User {
id: number
name: string
}
type ReadonlyUser = Readonly<User>
// { readonly id: number; readonly name: string }Conditional Types
// T extends U ? X : Y
type IsString<T> = T extends string ? true : false
type A = IsString<string> // true
type B = IsString<number> // false
// Bardziej praktyczny przykład
type ArrayElement<T> = T extends (infer U)[] ? U : never
type StringArray = string[]
type Element = ArrayElement<StringArray> // string
// Unwrap Promise
type Awaited<T> = T extends Promise<infer U> ? U : T
type PromiseString = Promise<string>
type JustString = Awaited<PromiseString> // stringTemplate Literal Types
// Typy oparte na string templates
type EventName = 'click' | 'focus' | 'blur'
type EventHandler = `on${Capitalize<EventName>}`
// 'onClick' | 'onFocus' | 'onBlur'
// Praktyczne użycie z API
type HttpMethod = 'get' | 'post' | 'put' | 'delete'
type ApiEndpoint = `/api/${string}`
type ApiRoute = `${Uppercase<HttpMethod>} ${ApiEndpoint}`
const route: ApiRoute = 'GET /api/users' // ✅
// const invalid: ApiRoute = 'PATCH /api/users' // ❌TypeScript z React
Komponenty funkcyjne
// Props interface
interface ButtonProps {
children: React.ReactNode
onClick?: () => void
variant?: 'primary' | 'secondary'
disabled?: boolean
}
// Funkcyjny komponent
function Button({ children, onClick, variant = 'primary', disabled }: ButtonProps) {
return (
<button
className={`btn-${variant}`}
onClick={onClick}
disabled={disabled}
>
{children}
</button>
)
}
// Lub z React.FC (mniej zalecane)
const Button: React.FC<ButtonProps> = ({ children, onClick }) => {
return <button onClick={onClick}>{children}</button>
}Hooks z TypeScript
// useState
const [count, setCount] = useState<number>(0)
const [user, setUser] = useState<User | null>(null)
// useRef
const inputRef = useRef<HTMLInputElement>(null)
const timerRef = useRef<number | null>(null)
// useReducer
interface State {
count: number
error: string | null
}
type Action =
| { type: 'increment' }
| { type: 'decrement' }
| { type: 'error'; payload: string }
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + 1 }
case 'decrement':
return { ...state, count: state.count - 1 }
case 'error':
return { ...state, error: action.payload }
}
}
const [state, dispatch] = useReducer(reducer, { count: 0, error: null })Event handlers
function Form() {
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value)
}
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
}
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
console.log(e.clientX, e.clientY)
}
return (
<form onSubmit={handleSubmit}>
<input onChange={handleChange} />
<button onClick={handleClick}>Submit</button>
</form>
)
}Konfiguracja - tsconfig.json
{
"compilerOptions": {
// Wersja docelowa JavaScript
"target": "ES2022",
// System modułów
"module": "ESNext",
"moduleResolution": "bundler",
// Ścisłe sprawdzanie typów (zalecane!)
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noUncheckedIndexedAccess": true,
// Ścieżki
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
},
// JSX dla React
"jsx": "react-jsx",
// Inne przydatne opcje
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}Best Practices
Do's
// ✅ Używaj strict mode
"strict": true
// ✅ Definiuj typy dla API responses
interface ApiResponse<T> {
data: T
status: number
}
// ✅ Używaj unknown zamiast any
function parseJSON(json: string): unknown {
return JSON.parse(json)
}
// ✅ Używaj const assertions
const config = {
api: '/api',
timeout: 5000
} as const
// ✅ Eksportuj typy razem z funkcjami
export interface User { /* ... */ }
export function getUser(): User { /* ... */ }Don'ts
// ❌ Unikaj any
function process(data: any) { } // Zła praktyka
// ❌ Nie ignoruj błędów TypeScript
// @ts-ignore
const x = badCode() // Zła praktyka
// ❌ Nie używaj ! bez potrzeby
const el = document.getElementById('app')!
// Lepiej: sprawdź czy istnieje
// ❌ Nie nadużywaj type assertions
const user = data as User // Preferuj type guardsFAQ
Czy TypeScript spowalnia development?
Początkowo tak, ale długoterminowo przyspiesza przez:
- Wykrywanie błędów wcześniej
- Lepsze autouzupełnianie
- Łatwiejszą refaktoryzację
Czy muszę typować wszystko?
Nie. TypeScript ma świetną inferencję typów. Typuj interfejsy publiczne, a wewnętrzny kod często nie wymaga explicit typów.
TypeScript vs JSDoc?
TypeScript oferuje znacznie więcej: generyki, utility types, conditional types. JSDoc jest OK dla prostych projektów.
Podsumowanie
TypeScript to must-have dla profesjonalnych projektów JavaScript:
- Type safety - Błędy wykryte w czasie kompilacji
- DX - Świetne autouzupełnianie i dokumentacja
- Refaktoryzacja - Bez strachu o regresje
- Ekosystem - Obsługiwany przez wszystkie major frameworks