Kompozycja z systemem HTML dla stron internetowych

Operatory ?? i ?. w JavaScript – nullish coalescing i optional chaining

10 min. czytania

Operatory ?? i ?. w JavaScript – nullish coalescing i optional chaining w praktyce (także z myślą o dostępności)

Nowoczesny JavaScript dostarcza dwóch operatorów, które upraszczają pracę z danymi mogącymi być null lub undefined: nullish coalescing (??) oraz optional chaining (?.). Oba pojawiły się w standardzie ES2020 i są szeroko wspierane w przeglądarkach oraz w TypeScript.

W artykule znajdziesz przegląd kluczowych zagadnień:

  • czym dokładnie są operatory ?? i ?.,
  • jak działają „pod maską”,
  • czym ?? różni się od || i kiedy używać którego,
  • jak łączyć ?. i ?? w zwięzłe, bezpieczne konstrukcje,
  • praktyczne przykłady z front‑endu oraz z obszaru dostępności (a11y),
  • na co uważać, aby nie zamaskować realnych błędów w aplikacji.

1. Skąd się wzięły ?? i ?.?

JavaScript od zawsze pozwalał pisać kod, który „wybucha” w najmniej oczekiwanym momencie, gdy spróbujesz odwołać się do właściwości undefined lub null:

const user = null;
console.log(user.profile.name); // TypeError: Cannot read properties of null

Aplikacje webowe coraz częściej korzystają z następujących źródeł i mechanizmów, co zwiększa niepewność danych:

  • zewnętrznych API,
  • dynamicznych danych,
  • złożonych konfiguracji (w tym ustawień dostępności użytkownika).

Nie możesz zakładać, że wszystkie dane zawsze istnieją. Wcześniej stosowało się zagnieżdżone instrukcje if lub sekwencje &&:

const theme = user && user.settings && user.settings.theme;

To działa, ale bywa mało czytelne i podatne na błędy. Aby rozwiązać ten problem, do języka dodano dwa operatori ułatwiające bezpieczny dostęp i sensowne domyślne wartości:

  • Optional chaining ?. – bezpieczny dostęp do głębokich właściwości i funkcji, bez rzucania błędów przy null/undefined;
  • Nullish coalescing ?? – ustawianie domyślnych wartości tylko wtedy, gdy coś jest null lub undefined, a nie gdy jest „falsy” (np. 0 czy pusty string).

2. Nullish coalescing ?? – „mądrzejsze” wartości domyślne

Definicja i składnia

Operator ?? zwraca prawy operand wyłącznie wtedy, gdy lewy jest null lub undefined; w przeciwnym razie zwraca lewy operand.

Składnia:

wartosc = wyrazenieLewo ?? wyrazeniePrawo;

Prosty przykład ustawienia nazwy:

const name = userInput ?? 'Anonim';

Jeśli userInput jest null lub undefined, name przyjmie wartość 'Anonim'; w innym przypadku zachowa wartość userInput.

Różnica między ?? a ||

|| traktuje każdą wartość „falsy” (0, '', false, NaN, null, undefined) jako powód, by użyć prawej strony, natomiast ?? reaguje wyłącznie na null oraz undefined. To kluczowe m.in. dla 0 czy pustych stringów, które mogą być poprawnymi danymi.

Przykład – limit stron w paginacji

const config = { pageSize: 0 };

const sizeWithOr = config.pageSize || 10;
const sizeWithNullish = config.pageSize ?? 10;

console.log(sizeWithOr); // 10 (0 jest traktowane jako falsy)
console.log(sizeWithNullish); // 0 (0 NIE jest nullish)

W tym przykładzie || „zepsuje” wartość 0, traktując ją jak brak wartości; ?? zachowa 0, bo nie jest null ani undefined.

Kiedy używać ??, a kiedy ||?

Sięgnij po ??, gdy zależy Ci na realnym braku wartości (null/undefined) i świadomym zachowaniu danych. Oto typowe zastosowania:

  • konfiguracje (limity, timeouty, itp.),
  • dane numeryczne, gdzie 0 jest poprawną wartością,
  • stringi, gdy pusty string ma znaczenie (np. „pusty tytuł” jako świadomy wybór).

Użyj ||, jeśli chcesz zareagować na dowolną wartość „falsy”. Najczęstsze scenariusze:

  • brak loginu → wyświetl etykietę „Gość”,
  • brak parametru → użyj domyślnego tekstu,
  • wartość pusta/zerowa w UI → pokaż przyjazny fallback.

Przykład różnicy na stringach:

const labelFromApi = ''; // API zwróciło pusty string – świadomie

const labelOr = labelFromApi || 'Domyślna etykieta';
const labelNullish = labelFromApi ?? 'Domyślna etykieta';

console.log(labelOr); // "Domyślna etykieta"
console.log(labelNullish); // "" (pusty string zostaje zachowany)

Praktyczne przykłady

Domyślny tekst dla elementu interfejsu

const settings = {
fontScale: null,
highContrast: false,
};

const fontScale = settings.fontScale ?? 1.0; // 1.0 tylko jeśli null/undefined
const highContrast = settings.highContrast ?? true; // false zostaje zachowane, bo nie jest nullish

Parsowanie ustawień dostępności z API

const userA11y = apiResponse.accessibility ?? {};
const prefersReducedMotion = userA11y.prefersReducedMotion ?? false;
const prefersHighContrast = userA11y.prefersHighContrast ?? false;

Dzięki ?? zachowujesz rozróżnienie między „wartość świadomie ustawiona na false” a „brak wartości w ogóle”.

?? a operatory && / ||

Specyfikacja JS zabrania mieszania ?? bezpośrednio z && lub || bez nawiasów. Takie wyrażenia są niepoprawne składniowo:

// to jest niepoprawne składniowo:
a || b ?? c;

Zawsze dodawaj nawiasy, aby uniknąć niejednoznaczności:

a || (b ?? c);
(a ?? b) || c;

3. Optional chaining ?. – bezpieczne odwołania do zagnieżdżonych danych

Definicja i idea

Optional chaining ?. działa jak „bezpieczna kropka” lub „bezpieczne wywołanie funkcji” – gdy wartość po lewej jest null lub undefined, całe wyrażenie kończy się i zwraca undefined zamiast wyjątku.

Podstawowe formy składni

Możesz użyć optional chaining w trzech głównych formach:

obj?.prop // dostęp do właściwości
obj?.[expr] // dostęp z indeksem / wyrażeniem
obj.func?.(arg) // bezpieczne wywołanie funkcji

  • obj?.prop – próbuje odczytać prop, ale tylko jeśli obj nie jest nullish,
  • obj?.[expr] – to samo, ale gdy nazwa właściwości jest obliczana,
  • obj.func?.(arg) – wywołuje funkcję tylko, jeśli obj.func istnieje i nie jest nullish.

Jeżeli którykolwiek element w łańcuchu optional chaining jest null/undefined, całe wyrażenie zwraca undefined.

Przykład z głęboko zagnieżdżonym obiektem

Bez optional chaining:

const theme = user && user.settings && user.settings.preferences && user.settings.preferences.theme;

Z optional chaining:

const theme = user?.settings?.preferences?.theme;

Jeśli user lub settings lub preferences będzie null albo undefined, theme stanie się undefined, ale nie zostanie rzucony błąd.

Optional chaining z tablicami i indeksami

Dostęp do elementu tablicy, gdy nie masz pewności, czy tablica istnieje:

const firstItem = data?.items?.[0];

Jeśli data lub items są nullish, firstItem będzie undefined.

Optional chaining z funkcjami

Bezpieczne wywołanie funkcji, która może nie istnieć:

const logger = window.customLogger;
logger?.('Wiadomość testowa'); // wywoła tylko, jeśli logger jest funkcją

Zamiast:

if (typeof logger === 'function') {
logger('Wiadomość testowa');
}

Optional chaining skraca zapis i zwiększa czytelność kontroli istnienia funkcji.

Co dokładnie „przerywa” ?.?

Operator ?. działa jak bezpieczny przystanek:

  • jeśli wartość po lewej jest null lub undefined, wyrażenie kończy się i zwraca undefined,
  • kolejne części łańcucha nie są już sprawdzane,
  • nie jest rzucany żaden wyjątek.

Przykład:

const result = config?.user?.preferences?.getTheme?.();

Jeśli config.user jest undefined, interpreter zatrzyma się na config?.user i zwróci undefined dla całego wyrażenia.

4. Łączenie ?. i ?? – bezpieczne łańcuchy z domyślną wartością

Połączenie ?. z ?? daje zwięzłe i odporne konstrukcje: ?. bezpiecznie przerywa dostęp do brakujących właściwości, a ?? zapewnia sensowną wartość domyślną wyłącznie dla null/undefined.

Przykład – preferencje użytkownika z API

const theme = user?.settings?.preferences?.theme ?? 'light';

Jeśli któryś z elementów (user, settings, preferences) jest nullish, łańcuch po lewej zwróci undefined, a ?? 'light' wstawi domyślny motyw „light”.

Przykład – ustawienia dostępności

Załóżmy, że API może (ale nie musi) zwrócić ustawienia dostępności:

const a11y = user?.profile?.accessibility;
const prefersHighContrast = a11y?.highContrast ?? false;
const prefersReducedMotion = a11y?.reducedMotion ?? false;

Unikasz błędów „Cannot read properties of undefined” i zawsze masz sensowne domyślne (false), gdy brakuje konkretnych flag.

Bezpieczne wywołanie funkcji z domyślną wartością

const value = config?.parseValue?.(raw) ?? defaultValue;

Jeśli config lub parseValue są nullish, lewa strona zwróci undefined, a ?? defaultValue dostarczy wartość zastępczą.

5. Przykłady z front‑endu i dostępności (a11y)

Serwis poświęcony dostępności powinien szczególnie dbać o to, by błędy JS nie przerywały działania aplikacji, bo skutkiem mogą być:

  • niedziałające klawiszowe skróty,
  • niesprawne komponenty ARIA (dialogi, menu, zakładki),
  • brak komunikatów dla czytników ekranu.

Bezpieczne ustawianie atrybutów ARIA

Wyobraź sobie, że masz konfigurowalny komponent przycisku:

const config = getButtonConfig(); // może zwrócić obiekt niepełny
const label = config?.aria?.label ?? 'Otwórz menu';
button.setAttribute('aria-label', label);

config?.aria?.label pozwala bezpiecznie pobrać głęboko zagnieżdżoną właściwość, a ?? 'Otwórz menu' zapewnia czytelny domyślny tekst – dzięki temu przycisk zawsze ma sensowne aria-label.

Obsługa preferencji użytkownika przeglądarki

Odczyt preferencji „reduced motion” lub „dark mode” z obiektu konfiguracji lub z localStorage:

const userSettings = maybeParseJSON(localStorage.getItem('userSettings'));
const prefersReducedMotion = userSettings?.a11y?.reducedMotion ?? false;

if (prefersReducedMotion) {
document.documentElement.classList.add('reduced-motion');
}

Jeśli userSettings to null albo brak w nim sekcji a11y, kod nadal działa poprawnie przy inicjalizacji.

Bezpieczne logowanie i telemetria

System logowania nie powinien blokować działania aplikacji. Użyj optional chaining, by łagodnie pominąć brakujące integracje:

window.appTelemetry?.logEvent?.('A11Y_BUTTON_CLICK', {
time: Date.now(),
});

Gdy appTelemetry lub logEvent nie są zainicjalizowane (np. offline, środowisko testowe), wywołanie zostanie pominięte bez błędu, a kluczowe funkcje dostępności działają dalej bez zakłóceń.

6. Typowe pułapki i dobre praktyki

Nie nadużywaj optional chaining

Optional chaining jest wygodny, ale łatwo go nadużyć. Przykład, który może maskować krytyczny błąd:

// ZŁE: może maskować poważny błąd w strukturze danych
const criticalValue = data?.very?.deep?.path?.to?.value;

Jeśli dana powinna zawsze istnieć, nadużycie ?. może „uciszyć” realny problem (np. błąd API lub parsowania). Zamiast tego rzuć kontrolowany błąd lub wyświetl komunikat użytkownikowi.

Optional chaining nie jest „if‑em” dla wszystkiego

?. sprawdza tylko nullish (null/undefined). Jeśli np. user.settings jest pustym obiektem, user.settings?.theme zwróci undefined – nie dlatego, że settings jest „złe”, ale dlatego, że theme nie istnieje. Warto łączyć ?. z walidacją danych i jasną logiką biznesową.

Uważaj na ?? z innymi operatorami logicznymi

Nie mieszaj ?? z &&/|| bez nawiasów – to niezgodne ze składnią JS. Pisz wprost:

const value = (input ?? defaultValue) || 'fallback';

7. Wsparcie w TypeScript i narzędziach

TypeScript od wersji 3.7 obsługuje zarówno ?., jak i ??. Możesz używać tych operatorów w kodzie TS, a kompilator (tsc/Babel) przekształci je do form zgodnych ze starszymi środowiskami. W nowoczesnych projektach front‑endowych z bundlerami i transpilatorami ?? i ?. są standardem de facto.

8. Podsumowanie praktyczne

Najważniejsze rzeczy do zapamiętania:

  • ?? (nullish coalescing) – zwraca prawy operand tylko dla null/undefined; nie traktuje 0, '' ani false jako „braku wartości”; idealny do sensownych wartości domyślnych w konfiguracji i danych;
  • ?. (optional chaining) – bezpiecznie przechodzi po łańcuchach właściwości i wywołaniach funkcji; przy null/undefined zwraca undefined zamiast wyjątku; dostępne formy: obj?.prop, obj?.[expr], obj.func?.(args);
  • razem – używaj wzorca user?.settings?.theme ?? 'light' podczas pracy z danymi opcjonalnymi; szczególnie przydatne w kontekście dostępności, gdzie bezpieczny kod JS jest krytyczny.