|
| 1 | +# Optional chaining '?.' |
| 2 | + |
| 3 | +[recent browser="new"] |
| 4 | + |
| 5 | +Optional chaining `?.` pozwala nam w bezpieczny sposób odczytać zagłębione właściwości obiektu, nawet jeśli któraś z nich "po drodze" nie istnieje. |
| 6 | + |
| 7 | +## Problem nieistniejącej właściwości |
| 8 | + |
| 9 | +Jeśli dopiero zaczynasz czytać ten kurs i uczyć się JavaScriptu, być może nie doświadczyłeś jeszcze tego problemu, ale jest on dość powszechny. |
| 10 | + |
| 11 | +Jako przykład załóżmy że mamy objekt `user` w którym trzymamy informacje o naszych użytkownikach. |
| 12 | + |
| 13 | +Większość użytkowników posiada adres we właściwości `user.address`, wraz z nazwą ulicy `user.address.street`, lecz nie każdy użytkownik podał te dane. |
| 14 | + |
| 15 | +W tym wypadku, gdy spróbujemy odczytać właściwość `user.address.street`, a użytkownik nie podał swojego adresu, otrzymujemy błąd: |
| 16 | + |
| 17 | +```js run |
| 18 | +let user = {}; // użytkownik bez właściwości "address" |
| 19 | + |
| 20 | +alert(user.address.street); // Błąd! |
| 21 | +``` |
| 22 | + |
| 23 | +Jest to oczekiwany rezultat. Tak działa JavaScript. Jeśli `user.address` jest równe `undefined`, próba odczytania `user.address.street` kończy się niepowodzeniem i błędem. |
| 24 | + |
| 25 | +W wielu przypadkach wolelibyśmy otrzymać `undefined` zamiast błędu (co by znaczyło "brak ulicy"). |
| 26 | + |
| 27 | +...I kolejny przykład. W programowaniu webowym, możemy dostać objekt który odpowiada elementowi na stronie wywołując specjalną metodę, np. `document.querySelector('.elem')`, której wywołanie zwraca nam `null` jeśli element nie istnieje. |
| 28 | + |
| 29 | +```js run |
| 30 | +// document.querySelector('.elem') zwraca null jeśli element nie istnieje |
| 31 | +let html = document.querySelector(".elem").innerHTML; // wyrzuca błąd jeśli null |
| 32 | +``` |
| 33 | + |
| 34 | +Jeszcze raz, jeśli element nie istnieje, otrzymamy błąd próbując odczytać `.innerHTML` z `null`. W niektórych przypadkach, gdy brak elementu jest normalny, chcielibyśmy uniknąć błędu i po prostu zaakceptować `html = null` jako rezultat. |
| 35 | + |
| 36 | +Jak możemy to zrobić? |
| 37 | + |
| 38 | +Oczywistym rozwiązaniem jest sprawdzenie wartości przy użyciu instrukcji warunkowej `if` lub conditional operator `?` przed odczytaniem wartości, jak na przykładzie: |
| 39 | + |
| 40 | +```js |
| 41 | +let user = {}; |
| 42 | + |
| 43 | +alert(user.address ? user.address.street : undefined); |
| 44 | +``` |
| 45 | + |
| 46 | +Działa, nie otrzymujemy błędu... Lecz ten sposób jest mało elegancki. Jak możesz zobaczyć, `"user.address"` występuje w kodzie podwójnie. Dla bardziej zagłębionych właściwości, staje się to problemem ponieważ wymagana jest większa ilość powtórzeń. |
| 47 | + |
| 48 | +Np. spróbujmy odczytać `user.address.street.name`. |
| 49 | + |
| 50 | +Musimy sprawdzić `user.address` i `user.address.street`: |
| 51 | + |
| 52 | +```js |
| 53 | +let user = {}; // użytkownik nie posiada adresu |
| 54 | + |
| 55 | +alert(user.address ? (user.address.street ? user.address.street.name : null) : null); |
| 56 | +``` |
| 57 | + |
| 58 | +Wygląda to okropnie, można nawet mieć problemy ze zrozumieniem tego kodu. |
| 59 | + |
| 60 | +Nawet nie próbuj, ponieważ istnieje lepszy sposób na napisanie tego, używając operatora `&&`: |
| 61 | + |
| 62 | +```js run |
| 63 | +let user = {}; // użytkownik nie posiada adresu |
| 64 | + |
| 65 | +alert(user.address && user.address.street && user.address.street.name); // undefined (brak błędu) |
| 66 | +``` |
| 67 | + |
| 68 | +Używanie operatora AND przez całą drogę do właściwości, upewnia się że wszystkie komponenty istnieją (jeśli nie, zatrzymuje wyrażenie), jednak to rozwiązanie też nie jest idealne. |
| 69 | + |
| 70 | +Jak możesz zobaczyć, nazwy właściwości nadal występują w kodzie kilkukrotnie. Np. w kodzie powyżej, `user.address` występuje trzykrotnie. |
| 71 | + |
| 72 | +Właśnie dlatego optional chaining `?.` został dodany do składni języka. By rozwiązać ten problem raz na zawsze! |
| 73 | + |
| 74 | +## Optional chaining |
| 75 | + |
| 76 | +Optional chaining `?.` zatrzymuje wyrażenie jeśli wartość poprzedzająca `?.` jest równa `undefined` lub `null` i zwraca `undefined`. |
| 77 | + |
| 78 | +**W dalszej części artykułu, dla zwięzłości, powiemy że coś "istnieje" jeśli nie jest równe `null` ani `undefined`.** |
| 79 | + |
| 80 | +Inaczej mówiąc, `value?.prop`: |
| 81 | + |
| 82 | +- działa jak `value.prop`, jeśli `value` istnieje, |
| 83 | +- w innym wypadku (gdy `value` jest równe `undefined/null`) zwraca `undefined`. |
| 84 | + |
| 85 | +Tak wygląda bezpieczny sposób odczytania wartości `user.address.street` przy użyciu `?.`: |
| 86 | + |
| 87 | +```js run |
| 88 | +let user = {}; // użytkownik nie posiada adresu |
| 89 | + |
| 90 | +alert(user?.address?.street); // undefined (brak błędu) |
| 91 | +``` |
| 92 | +
|
| 93 | +Kod jest zwięzły i czysty, nie występują żadne powtórzenia. |
| 94 | +
|
| 95 | +Odczytanie adresu jako `user?.address` działa nawet jeśli objekt `user` nie istnieje: |
| 96 | +
|
| 97 | +```js run |
| 98 | +let user = null; |
| 99 | + |
| 100 | +alert(user?.address); // undefined |
| 101 | +alert(user?.address.street); // undefined |
| 102 | +``` |
| 103 | +
|
| 104 | +Miej to na uwadze: składnia `?.` traktuje tylko wartość przed sobą jako opcjonalną, ale nie kolejne. |
| 105 | +
|
| 106 | +Np. w `user?.address.street.name` składnia `?.` pozwala wartości `user` być równą `null/undefined` (zwraca `undefined` w tym wypadku), ale to działa tylko dla właściwości `user`. Kolejne właściwości są odczytywane zwyczajnie. Jeśli chcemy by niektóre z nich były opcjonalne, wtedy musimy zamienić więcej `.` na `?.`. |
| 107 | +
|
| 108 | +```warn header="Nie nadużywaj składni optional chaining" |
| 109 | +Powinniśmy używać `?.` tylko gdy coś może nie istnieć. |
| 110 | + |
| 111 | +Jako przykład, jeśli zgodnie z naszą logiką kodowania objekt `user` musi istnieć, ale `address` jest opcjonalny, wtedy powinniśmy zapisać `user.address?.street`, ale nie `user?.address?.street`. |
| 112 | + |
| 113 | +Więc, jeśli przez przypadek `user` nie będzie zdefiniowany, zobaczymy błąd i go naprawimy. W innym wypadku, błędy mogą zostać wyciszone gdy nie powinny, i staną się trudniejsze do naprawienia. |
| 114 | +``` |
| 115 | +
|
| 116 | +````warn header="Zmienna przed `?.`musi być zadeklarowana" Jeśli zmienna`user`nie istnieje, wtedy`user?.anything` wyrzuca błąd: |
| 117 | +
|
| 118 | +```js run |
| 119 | +// ReferenceError: zmienna user nie jest zadeklarowana |
| 120 | +user?.address; |
| 121 | +``` |
| 122 | +
|
| 123 | +Zmienna musi być zadeklarowana (np. `let/const/var user` lub jako parametr funkcji). Optional chaining działa tylko dla zadeklarowanych zmiennych. |
| 124 | + |
| 125 | +````` |
| 126 | +
|
| 127 | +## Short-circuiting |
| 128 | +
|
| 129 | +Jak zostało powiedziane wcześniej, `?.` natychmiast zatrzymuje ("short-circuits") wykonanie jeśli rodzic po lewej nie istnieje. |
| 130 | +
|
| 131 | +Więc, jeśli występują jakieś dalsze wywołania funkcji lub efekty uboczne, nie zostaną one wykonane. |
| 132 | +
|
| 133 | +Na przykład: |
| 134 | +
|
| 135 | +```js run |
| 136 | +let user = null; |
| 137 | +let x = 0; |
| 138 | + |
| 139 | +user?.sayHi(x++); // brak "sayHi", więc x++ nie zostanie wykonane |
| 140 | + |
| 141 | +alert(x); // 0, wartość nie została zwiększona |
| 142 | +``` |
| 143 | +
|
| 144 | +## Inne warianty: ?.(), ?.[] |
| 145 | +
|
| 146 | +Optional chaining `?.` nie jest operatorem, lecz specjalnym znakiem składni, który działa również z funkcjami i nawiasami kwadratowymi. |
| 147 | +
|
| 148 | +Na przykład, `?.()` jest używane do wywołania funkcji która może nie istnieć. |
| 149 | +
|
| 150 | +W kodzie poniżej, niektórzy z naszych użytkowników posiadają metodę `admin`, a niektórzy nie: |
| 151 | +
|
| 152 | +```js run |
| 153 | +let userAdmin = { |
| 154 | + admin() { |
| 155 | + alert("Jestem administratorem"); |
| 156 | + } |
| 157 | +}; |
| 158 | + |
| 159 | +let userGuest = {}; |
| 160 | + |
| 161 | +*!* |
| 162 | +userAdmin.admin?.(); // Jestem administratorem |
| 163 | +*/!* |
| 164 | + |
| 165 | +*!* |
| 166 | +userGuest.admin?.(); // nic (brak metody) |
| 167 | +*/!* |
| 168 | +``` |
| 169 | +
|
| 170 | +W tym wypadku, w obu liniach najpierw użyliśmy kropki (`userAdmin.admin`) aby odczytać wartość `admin`, ponieważ zakładamy że objekt użytkownika istnieje, więc bezpiecznie jest odczytać z niego wartość. |
| 171 | +
|
| 172 | +Następnie `?.()` sprawdza lewą część: jeśli funkcja admin istnieje, wtedy zostaje wywołana (tak się dzieje w przypadku `userAdmin`). W innym wypadku (dla `userGuest`) wywołanie zatrzymuje się bez błędów. |
| 173 | +
|
| 174 | +Składnia `?.[]` również działa, jeśli chcielibyśmy użyć nawiasów kwadratowych `[]` aby odczytać właściwości zamiast kropki `.`. Podobnie do poprzednich przykładów, pozwala to w bezpieczny sposób odczytać wartość z objektu który może nie istnieć. |
| 175 | +
|
| 176 | +```js run |
| 177 | +let key = "firstName"; |
| 178 | + |
| 179 | +let user1 = { |
| 180 | + firstName: "John" |
| 181 | +}; |
| 182 | + |
| 183 | +let user2 = null; |
| 184 | + |
| 185 | +alert( user1?.[key] ); // John |
| 186 | +alert( user2?.[key] ); // undefined |
| 187 | +``` |
| 188 | +
|
| 189 | +Możemy również użyć `?.` w połączeniu z `delete`: |
| 190 | +
|
| 191 | +```js run |
| 192 | +delete user?.name; // usuń user.name jeśli user istnieje |
| 193 | +``` |
| 194 | +
|
| 195 | +````warn header="Możemy użyć `?.` aby bezpiecznie odczytywać i usuwać, ale nie przypisywać" |
| 196 | +Optional chaining `?.` nie ma zastosowania po lewej stronie przypisania. |
| 197 | +
|
| 198 | +For example: |
| 199 | +```js run |
| 200 | +let user = null; |
| 201 | + |
| 202 | +user?.name = "John"; // Błąd, nie działa |
| 203 | +// ponieważ jest to równoważne do undefined = "John" |
| 204 | +``` |
| 205 | +
|
| 206 | +Nie jest to na tyle zaawansowane. |
| 207 | +````` |
| 208 | + |
| 209 | +## Podsumowanie |
| 210 | + |
| 211 | +Składnia optional chaining `?.` ma trzy formy: |
| 212 | + |
| 213 | +1. `obj?.prop` -- zwraca `obj.prop` jeśli `obj` istnieje, w innym wypadku `undefined`. |
| 214 | +2. `obj?.[prop]` -- zwraca `obj[prop]` jeśli `obj` istnieje, w innym wypadku `undefined`. |
| 215 | +3. `obj.method?.()` -- wywołuje `obj.method()` jeśli `obj.method` istnieje, w innym wypadku zwraca `undefined`. |
| 216 | + |
| 217 | +Jak możemy zauważyć, wszystkie z nich są przystępne i proste w użyciu. Składnia `?.` sprawdza lewą część czy jest równa `null/undefined` i zezwala na wykonanie jeśli nie jest. |
| 218 | + |
| 219 | +Ciąg `?.` pozwala w bezpieczny sposób uzyskać dostęp do zagnieżdzonych właściwości. |
| 220 | + |
| 221 | +Mimo wszystko, powinniśmy używać `?.` ostrożnie, tylko gdy akceptujemy to że lewa strona może nie istnieć. Tak aby wszelkie błędy nie zostały przed nami ukryte, jeśli już wystąpią. |
0 commit comments