[[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モジュール]]