[[JavaScript]]のクエストもあと2つです。知っているかどうかが可読性を大きく左右する[[オプショナルチェーン (JavaScript)|オプショナルチェーン]]を学びます。 ## Reference - https://jsprimer.net/basic/object/#optional-chaining-operator ## Lesson [[オプショナルチェーン (JavaScript)|オプショナルチェーン]]は参照が[[Nullish (JavaScript)|Nullish]]な場合に、エラーを起こさず[[undefined (JavaScript)|undefined]]を返す演算子です。参照が存在する場合は通常通り値にアクセスします。 構文は `.`アクセスの直前に `?` をつけて `?.` のように扱います。 ```js {name: "hoge"}.name // name {name: "hoge"}?.name // name undefined.name // エラー undefined?.name // undefined null.name // エラー null?.name // undefined ``` ### コード例 実用的なコードを見てみましょう。 ```js function getRandomObj() { return Math.random() > 0.5 ? { id: 1, name: "tadashi" } : { id: 2 }; } const nameLength = getRandomObj().name.length; console.log(nameLength); ``` `getRandomObj()` はそれぞれ50%の確率で異なる値を返します。片方の値には `name` [[プロパティ (JavaScript)|プロパティ]]は存在しないため、`getRandomObj().name.length` は50%の確率でエラーになります。 ```error error: Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'length') const nameLength = getRandomObj().name.length; ^ at file:///home/tadashi-aikawa/tmp/tdq/main.js:5:39 ``` この場合、[[論理積演算子 (JavaScript)|論理積演算子]]やif文を使った処理が必要です。 ```js const name = getRandomObj().name; const nameLength = name != null && name.length; console.log(nameLength); ``` しかし、[[オプショナルチェーン (JavaScript)|オプショナルチェーン]]を使えば、`?`を1文字足すだけで事足ります。 ```js const nameLength = getRandomObj().name?.length; console.log(nameLength); ``` > [!hint] > `name && name.length` とは書けません。空文字は [[Falsy (JavaScript)|Falsy]] であるため、`name` が空文字の場合 `nameLength` に空文字を返してしまうからです。 ## Mission 1 #😁EASY 以下のコードを[[オプショナルチェーン (JavaScript)|オプショナルチェーン]]を使ってリファクタリングしてください。 ```js // ここは敢えてそのままで let obj = {}; if (obj.result) { if (obj.result.count) { console.log(`${obj.result.count}件の結果がありました`); } } ``` %% 解答例 ```js let obj = {}; const count = obj.result?.count; if (count) { console.log(`${count}件の結果がありました`); } ``` %% ## Mission 2 #🙂NORMAL [[オプショナルチェーン (JavaScript)|オプショナルチェーン]]を使ってFIXMEコメントの部分を実装してください。 ```js function getUsers(empty) { return empty ? { users: [] } : { users: [{ id: 1, name: "tadashi" }, { id: 2, name: "masaharu" }] }; } function getSecondUserLength(empty) { // FIXME: getUsersを使ってテストが通るように実装する } ``` 以下のテストが通ればOKです。 ```js import { expect } from "jsr:@std/expect"; Deno.test("getUsers()で取得した2人目のユーザー名の文字数を返す", () => { expect(getSecondUserLength(false)).toBe(8); }); Deno.test("存在しない場合はundefinedを返す", () => { expect(getSecondUserLength(true)).toBeUndefined(); }); ``` %% 解答例 ```js function getSecondUserLength(empty) { return getUsers(empty).users[1]?.name.length; } ``` %% > [!hint]- Hint 1 > 何が[[Nullish (JavaScript)|Nullish]]になり得るかを見極めて[[オプショナルチェーン (JavaScript)|オプショナルチェーン]]を使いましょう。とりあえずすべてに付けるのは、とりあえずすべてにNullチェックを行うことと同じくらい愚かです。 ## Mission 3 #😵HARD 以下のコードを[[オプショナルチェーン (JavaScript)|オプショナルチェーン]]を使ってリファクタリングしてください。ただし、`totalFare` の実装が **return文のみ(1文)** になるようにしてください。 ```js function totalFare(obj) { if (!obj.items) { return 0; } let fare = 0; for (const item of obj.items) { if (!item.summary || !item.summary.fare) { continue; } fare += item.summary.fare; } return fare; } ``` 以下のテストが通ればOKです。 ```js import { expect } from "jsr:@std/expect"; Deno.test("test1", () => { expect(totalFare({})).toBe(0); }); Deno.test("test2", () => { expect(totalFare({ items: [ { summary: { fare: 100 } }, { summary: { fare: 10 } }, { summary: { fare: 0 } }, { summary: {} }, {}, ], })).toBe(110); }); ``` %% 解答例 この実装が良いかどうかは別問題です。 ```js function totalFare(obj) { return obj.items ?.map((x) => x.summary?.fare ?? 0) .reduce((ac, x) => ac + x) ?? 0; } ``` %% > [!hint]- Hint 1 > `undefined` になる箇所は[[Null合体演算子 (JavaScript)|Null合体演算子]]で凌ぎます。 > [!hint]- Hint 2 > [[📗TDQ-016 配列の変換 その1]] と [[📗TDQ-017 配列の変換 その2]] で習得したメソッドを使って[[メソッドチェーン]]を決めましょう。 --- *NEXT* >> [[📗TDQ-029 ECMAScriptモジュール]]