- Notifications
You must be signed in to change notification settings - Fork30
Optional chaining '?.'#103
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Merged
deivuss331 merged 1 commit intojavascript-tutorial:masterfromdeivuss331:optional-chainingOct 19, 2021
Uh oh!
There was an error while loading.Please reload this page.
Merged
Changes fromall commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Jump to file
Failed to load files.
Loading
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
221 changes: 221 additions & 0 deletions1-js/04-object-basics/07-optional-chaining/article.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,221 @@ | ||
# Optional chaining '?.' | ||
[recent browser="new"] | ||
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. | ||
## Problem nieistniejącej właściwości | ||
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. | ||
Jako przykład załóżmy że mamy objekt `user` w którym trzymamy informacje o naszych użytkownikach. | ||
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. | ||
W tym wypadku, gdy spróbujemy odczytać właściwość `user.address.street`, a użytkownik nie podał swojego adresu, otrzymujemy błąd: | ||
```js run | ||
let user = {}; // użytkownik bez właściwości "address" | ||
alert(user.address.street); // Błąd! | ||
``` | ||
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. | ||
W wielu przypadkach wolelibyśmy otrzymać `undefined` zamiast błędu (co by znaczyło "brak ulicy"). | ||
...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. | ||
```js run | ||
// document.querySelector('.elem') zwraca null jeśli element nie istnieje | ||
let html = document.querySelector(".elem").innerHTML; // wyrzuca błąd jeśli null | ||
``` | ||
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. | ||
Jak możemy to zrobić? | ||
Oczywistym rozwiązaniem jest sprawdzenie wartości przy użyciu instrukcji warunkowej `if` lub conditional operator `?` przed odczytaniem wartości, jak na przykładzie: | ||
```js | ||
let user = {}; | ||
alert(user.address ? user.address.street : undefined); | ||
``` | ||
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ń. | ||
Np. spróbujmy odczytać `user.address.street.name`. | ||
Musimy sprawdzić `user.address` i `user.address.street`: | ||
```js | ||
let user = {}; // użytkownik nie posiada adresu | ||
alert(user.address ? (user.address.street ? user.address.street.name : null) : null); | ||
``` | ||
Wygląda to okropnie, można nawet mieć problemy ze zrozumieniem tego kodu. | ||
Nawet nie próbuj, ponieważ istnieje lepszy sposób na napisanie tego, używając operatora `&&`: | ||
```js run | ||
let user = {}; // użytkownik nie posiada adresu | ||
alert(user.address && user.address.street && user.address.street.name); // undefined (brak błędu) | ||
``` | ||
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. | ||
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. | ||
Właśnie dlatego optional chaining `?.` został dodany do składni języka. By rozwiązać ten problem raz na zawsze! | ||
## Optional chaining | ||
Optional chaining `?.` zatrzymuje wyrażenie jeśli wartość poprzedzająca `?.` jest równa `undefined` lub `null` i zwraca `undefined`. | ||
**W dalszej części artykułu, dla zwięzłości, powiemy że coś "istnieje" jeśli nie jest równe `null` ani `undefined`.** | ||
Inaczej mówiąc, `value?.prop`: | ||
- działa jak `value.prop`, jeśli `value` istnieje, | ||
- w innym wypadku (gdy `value` jest równe `undefined/null`) zwraca `undefined`. | ||
Tak wygląda bezpieczny sposób odczytania wartości `user.address.street` przy użyciu `?.`: | ||
```js run | ||
let user = {}; // użytkownik nie posiada adresu | ||
alert(user?.address?.street); // undefined (brak błędu) | ||
``` | ||
Kod jest zwięzły i czysty, nie występują żadne powtórzenia. | ||
Odczytanie adresu jako `user?.address` działa nawet jeśli objekt `user` nie istnieje: | ||
```js run | ||
let user = null; | ||
alert(user?.address); // undefined | ||
alert(user?.address.street); // undefined | ||
``` | ||
Miej to na uwadze: składnia `?.` traktuje tylko wartość przed sobą jako opcjonalną, ale nie kolejne. | ||
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 `?.`. | ||
```warn header="Nie nadużywaj składni optional chaining" | ||
Powinniśmy używać `?.` tylko gdy coś może nie istnieć. | ||
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`. | ||
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. | ||
``` | ||
````warn header="Zmienna przed `?.`musi być zadeklarowana" Jeśli zmienna`user`nie istnieje, wtedy`user?.anything` wyrzuca błąd: | ||
```js run | ||
// ReferenceError: zmienna user nie jest zadeklarowana | ||
user?.address; | ||
``` | ||
Zmienna musi być zadeklarowana (np. `let/const/var user` lub jako parametr funkcji). Optional chaining działa tylko dla zadeklarowanych zmiennych. | ||
````` | ||
## Short-circuiting | ||
Jak zostało powiedziane wcześniej, `?.` natychmiast zatrzymuje ("short-circuits") wykonanie jeśli rodzic po lewej nie istnieje. | ||
Więc, jeśli występują jakieś dalsze wywołania funkcji lub efekty uboczne, nie zostaną one wykonane. | ||
Na przykład: | ||
```js run | ||
let user = null; | ||
let x = 0; | ||
user?.sayHi(x++); // brak "sayHi", więc x++ nie zostanie wykonane | ||
alert(x); // 0, wartość nie została zwiększona | ||
``` | ||
## Inne warianty: ?.(), ?.[] | ||
Optional chaining `?.` nie jest operatorem, lecz specjalnym znakiem składni, który działa również z funkcjami i nawiasami kwadratowymi. | ||
Na przykład, `?.()` jest używane do wywołania funkcji która może nie istnieć. | ||
W kodzie poniżej, niektórzy z naszych użytkowników posiadają metodę `admin`, a niektórzy nie: | ||
```js run | ||
let userAdmin = { | ||
admin() { | ||
alert("Jestem administratorem"); | ||
} | ||
}; | ||
let userGuest = {}; | ||
*!* | ||
userAdmin.admin?.(); // Jestem administratorem | ||
*/!* | ||
*!* | ||
userGuest.admin?.(); // nic (brak metody) | ||
*/!* | ||
``` | ||
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ść. | ||
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. | ||
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ć. | ||
```js run | ||
let key = "firstName"; | ||
let user1 = { | ||
firstName: "John" | ||
}; | ||
let user2 = null; | ||
alert( user1?.[key] ); // John | ||
alert( user2?.[key] ); // undefined | ||
``` | ||
Możemy również użyć `?.` w połączeniu z `delete`: | ||
```js run | ||
delete user?.name; // usuń user.name jeśli user istnieje | ||
``` | ||
````warn header="Możemy użyć `?.` aby bezpiecznie odczytywać i usuwać, ale nie przypisywać" | ||
Optional chaining `?.` nie ma zastosowania po lewej stronie przypisania. | ||
For example: | ||
```js run | ||
let user = null; | ||
user?.name = "John"; // Błąd, nie działa | ||
// ponieważ jest to równoważne do undefined = "John" | ||
``` | ||
Nie jest to na tyle zaawansowane. | ||
````` | ||
## Podsumowanie | ||
Składnia optional chaining `?.` ma trzy formy: | ||
1. `obj?.prop` -- zwraca `obj.prop` jeśli `obj` istnieje, w innym wypadku `undefined`. | ||
2. `obj?.[prop]` -- zwraca `obj[prop]` jeśli `obj` istnieje, w innym wypadku `undefined`. | ||
3. `obj.method?.()` -- wywołuje `obj.method()` jeśli `obj.method` istnieje, w innym wypadku zwraca `undefined`. | ||
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. | ||
Ciąg `?.` pozwala w bezpieczny sposób uzyskać dostęp do zagnieżdzonych właściwości. | ||
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ą. |
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.