Nowoczesny JavaScript daje sporo konstrukcji, które skracają boilerplate i lepiej pokazują intencję, tyle że wiele projektów nadal pisze defensywny kod w stylu ES5, mimo że język od dawna oferuje prostsze i znacznie czytelniejsze idiomy.
W tym artykule zebrałem 10 technik, które realnie poprawiają czytelność kodu i nie chodzi tutaj o bycie sprytnym za wszelką cenę, lecz o to, żeby zapis był krótszy tam, gdzie ma to sens, i bardziej oczywisty dla osoby, która będzie go czytać kiedyś po Tobie.
1. Optional chaining — koniec z && && &&
Klasyczny problem: dostęp do zagnieżdżonych właściwości, które mogą nie istnieć.
Code
// ❌ Stary sposób jest brzydki i rozwlekłyconst street = user && user.address && user.address.street// ❌ Jeszcze gorszy sposób tolet streetif (user) { if (user.address) { street = user.address.street }}// ✅ Optional chaining — czysto i bezpiecznieconst street = user?.address?.street
Działa też z metodami oraz tablicami:
Code
// Metodyconst result = api?.getData?.()// Tabliceconst firstItem = arr?.[0]// Kombinacjaconst city = users?.[0]?.address?.city
Jeśli którykolwiek element jest null lub undefined, wyrażenie zwraca undefined zamiast rzucać błąd i jest to świetny zamiennik dla defensywnego łańcucha sprawdzeń, tylko nie używaj go też bezrefleksyjnie: jeśli dana właściwość musi istnieć, ciche undefined może ukryć realny problem z danymi.
2. Nullish coalescing — lepszy fallback niż ||
Operator || ma problem: traktuje 0, '' i false jako falsy.
Code
// ❌ Problem z ||const count = userCount || 10 // jeśli userCount = 0, dostaniesz 10!const name = userName || 'Anonymous' // jeśli userName = '', dostaniesz 'Anonymous'// ✅ Nullish coalescing — reaguje tylko na null/undefinedconst count = userCount ?? 10 // jeśli userCount = 0, dostaniesz 0 ✓const name = userName ?? 'Anonymous' // jeśli userName = '', dostaniesz '' ✓
Różnica jest subtelna, ale krytyczna:
|| — fallback gdy wartość jest falsy (0, '', false, null, undefined)
?? — fallback tylko gdy wartość jest null lub undefined
Najczęściej najlepiej działa to w parze z optional chaining:
Code
const city = user?.address?.city ?? 'Nieznane miasto'
3. Destructuring z domyślnymi wartościami
Destrukturyzacja szybko skraca kod, ale warto pamiętać o jednym szczególe, czyli że sama w sobie nie chroni przed null i undefined.
Code
// ❌ Ręczne sprawdzanie każdego pola osobnoconst name = user.name === undefined ? 'Unknown' : user.nameconst age = user.age === undefined ? 0 : user.ageconst role = user.role === undefined ? 'user' : user.role// ✅ Destructuring z defaults i bezpiecznym fallbackiemconst { name = 'Unknown', age = 0, role = 'user' } = user ?? {}// ✅ W parametrach funkcji — jeszcze lepiejfunction createUser({ name = 'Unknown', age = 0, role = 'user' } = {}) { return { name, age, role }}createUser({ name: 'Jan' }) // { name: 'Jan', age: 0, role: 'user' }createUser() // { name: 'Unknown', age: 0, role: 'user' }
Zauważ = {} na końcu, które jest zabezpieczeniem na wypadek, gdy funkcja zostanie wywołana bez argumentów.
Ważny detal: default w destrukturyzacji działa tylko dla undefined, nie dla null. Jeśli chcesz fallback również dla null, użyj ?? albo zastosuj user ?? {} jak w przykładzie wyżej.
4. Object shorthand, czyli zdecydowanie mniej pisania
W sytuacji, kiedy nazwa zmiennej jest taka sama jak klucz obiektu:
Code
const name = 'Jan'const age = 30const city = 'Kraków'// ❌ Powtórzeniaconst user = { name: name, age: age, city: city,}// ✅ Shorthandconst user = { name, age, city }
Działa także z metodami:
Code
// ❌ Stary sposóbconst calculator = { add: function (a, b) { return a + b },}// ✅ Shorthand methodsconst calculator = { add(a, b) { return a + b },}
To drobiazg, ale w return, payloadach API, czyli Application Programming Interface, definiuje sposób komunikacji między aplikacjami lub modułami. Tu chodzi o konwencje plików Next.js, które zamieniają eksportowaną funkcję w gotowy plik sitemap.xml lub robots.txt. i konfiguracjach ten zapis pojawia się cały czas, a im mniej mechanicznego powtarzania, tym łatwiej wyłapać naprawdę istotne fragmenty.
5. Spread operator do klonowania i łączenia
W większości codziennych przypadków spread jest najczytelniejszym sposobem na skopiowanie lub złożenie danych:
// find — pierwszy pasujący elementconst jan = users.find((u) => u.name === 'Jan')// some — czy którykolwiek spełnia warunekconst hasActive = users.some((u) => u.active) // true// every — czy wszystkie spełniają warunekconst allActive = users.every((u) => u.active) // false// reduce — agregacjaconst totalAge = users.reduce((sum, u) => sum + u.age, 0) // 90
Nie traktuj tego jak dogmatu, ponieważ jeśli potrzebujesz break, continue, wielu efektów ubocznych albo sekwencyjnego await, zwykłe for...of często będzie czytelniejsze niż wciskanie wszystkiego na siłę w map() czy też reduce().
7. Template literals — koniec z konkatenacją
To jedna z tych zmian, które po prostu warto wprowadzić na stałe, ponieważ interpolacja stringów jest czytelniejsza niż +:
Code
const name = 'Jan'const age = 30// ❌ Konkatenacjaconst message = 'Użytkownik ' + name + ' ma ' + age + ' lat.'// ✅ Template literalconst message = `Użytkownik ${name} ma ${age} lat.`
W tym wypadku, prawdziwa moc to multiline strings i wyrażenia:
Code
// Multiline bez \nconst html = ` <div class="card"> <h2>${user.name}</h2> <p>${user.bio ?? 'Brak opisu'}</p> </div>`// Wyrażenia w środkuconst status = `Status: ${isActive ? 'Aktywny' : 'Nieaktywny'}`// Tagged templates (zaawansowane)const query = sql`SELECT * FROM users WHERE id = ${userId}`
Najlepiej sprawdzają się w komunikatach, URL-ach, klasach CSS i prostych szablonach, a jeśli budujesz duże fragmenty HTML w aplikacji React, zwykle lepiej wejść poziom wyżej i renderować JSX zamiast składać stringi ręcznie.
8. Logical assignment operators
Nowe operatory przypisania z logiką warunkową:
Code
// ❌ Stary sposóbif (user.name == null) { user.name = 'Anonymous'}// ✅ Nullish assignmentuser.name ??= 'Anonymous'// ✅ Logical AND assignmentuser.data &&= processData(user.data) // przetwarza TYLKO jeśli data istnieje// ✅ Możesz też świadomie użyć ||=, jeśli pusty string/0/false mają być traktowane jako brak wartościuser.nickname ||= 'Anonymous'// ✅ Kolejny przykład ??=user.settings ??= getDefaultSettings() // przypisuje tylko jeśli null/undefined
Trzy operatory:
||= — przypisz jeśli falsy ('', 0, false, null, undefined)
&&= — przypisz jeśli truthy
??= — przypisz jeśli null/undefined
To bardzo wygodny zapis przy inicjalizacji danych i normalizacji payloadów, ale uważaj tylko tam, gdzie mutacja obiektu jest niepożądana, bo te operatory nadal modyfikują istniejącą referencję.
9. Object.entries/fromEntries — transformacja obiektów
Przekształcanie obiektów staje się wtedy banalnie proste:
const arr = [1, 2, 3, 4, 5]// ❌ Stary sposóbconst last = arr[arr.length - 1] // 5const secondLast = arr[arr.length - 2] // 4// ✅ Array.at() z negatywnym indeksemconst last = arr.at(-1) // 5const secondLast = arr.at(-2) // 4
To drobnostka, ale poprawia czytelność wszędzie tam, gdzie często sięgasz po ostatni element: historii, breadcrumbsach, kolejkach czy wynikach sortowania i uwaga - działa też ze stringami:
To nowość ES2024, więc jeśli wspierasz starsze środowiska, sprawdź kompatybilność albo dodaj polyfill.
Nie chodzi o to, że reduce() nagle stał się złym rozwiązaniem, ale po prostu w wielu przypadkach Object.groupBy() mówi wprost, co robisz, więc wygrywa lepszą czytelnością.
Bonus: toSorted() zamiast mutowania tablicy
W komponentach React łatwo przypadkiem posortować tablicę pochodzącą z propsów albo stanu. sort() zmienia oryginał, natomiast toSorted() zwraca nową tablicę:
Maciej Sala — project manager i frontendowiec z doświadczeniem w marketingu internetowym. Na co dzień pracuję z Reactem, Next.js i TypeScriptem, łącząc perspektywę produktową z praktycznym podejściem do kodu. Przez kilka lat związany z branżą gier wideo jako project manager i game designer.
Absolwent historii na Uniwersytecie Jagiellońskim i studiów podyplomowych z marketingu internetowego na Akademii Górniczo-Hutniczej w Krakowie. Poza pracą trenuje na siłowni, maluje figurki i realizuje własne projekty.
Sekwencyjne await potrafią niepotrzebnie wydłużyć czas odpowiedzi aplikacji. Zobacz, kiedy użyć Promise.all(), kiedy Promise.allSettled(), a kiedy ograniczyć równoległość.
Czym jest GEO i jak zwiększyć szansę, że Twoje treści będą cytowane przez ChatGPT, Perplexity i Google AI Overviews? Praktyczny przewodnik dla developerów: treść, architektura, boty, schema, pomiar i realne ograniczenia.