Skip to content

Commit 7a79cd3

Browse files
authored
Merge pull request #103 from deivuss331/optional-chaining
Optional chaining '?.'
2 parents 57f1e9e + 815392c commit 7a79cd3

File tree

1 file changed

+221
-0
lines changed
  • 1-js/04-object-basics/07-optional-chaining

1 file changed

+221
-0
lines changed
+221
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
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

Comments
 (0)