Next.js(React)でTypescriptで書いていますが、DOM関連でNullableな操作が続きどう処理するのがいいか悩んだので色々やってみます。
Typescriptは制限と同時にできることも多いので思いのほか色々できました。
キーワード:Null合体演算子Null条件演算子Null許容型Type GuardNon-null assertion operatorDefinite Assignment AssertionsOptional chaining
テストのためjsdomを使ってElement操作をします。
|
1 2 3 4 5 6 7 8 9 10 11 |
//lib for test //> yarn add jsdom @types/jsdom import { JSDOM } from "jsdom"; const { window: { document } } = new JSDOM( `<div><p id="p1">paragraph 1</p><p id="p2">paragraph 2<span id="s1">span<span></p></div>` ); const result = document.querySelector("#p1").textContent; console.log(result); |
10行目でエラー:オブジェクトは ‘null’ である可能性があります。ts(2531)
querySelector()は要素がない場合nullを返すためNullableなことが原因です。
問題をわかりやすくするため分けてみます。
|
1 2 |
const p1 = document.querySelector("#p1"); const result = p1.textContent; |
p1は Element | null なunion型です。
その場しのぎ
常にnullではなくtextContentがあるならいいわけです。
方法は様々ですがTypescriptにはNull合体演算子があるので使ってみます。
|
1 2 3 |
const p1 = document.querySelector("#NotfoundID") ?? { textContent: "not found" }; const result = p1.textContent; console.log(result); //not found |
A ?? BはAがnull, undefined(void 0)ならBを返す。見た目の似ている三項演算子(A?B:C)や役割の似ているor演算子(A||B)と違ってfalsyかどうかでない点は注意。
Type Guard
union型の型を決定して使うためにType Guardというものがあります。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
const p1 = document.querySelector("#p1"); //Type Guard if (p1 != null) { const result = p1.textContent; console.log(result); console.log(p1 instanceof Element); } //User-Defined Type Guards const isNull = (t: any): t is null => t == null; if (!isNull(p1)) { const result = p1.textContent; console.log(result); } |
nullじゃない状況を作って使えばエラーは出ません。
分かりやすいしミスもしなくなるため素晴らしいと思いますが、javascriptによって自由奔放に育てられた人には煩わしくも感じます。
少し強引ですが以下のようにしないといけないコードがあるとします。
|
1 |
const span = document.querySelector("#p2").querySelector("span"); |
Nullableが連なる処理をType Guardで書くのは結構面倒です。
Non-null assertion operator 🔗
通常初期化されていないなどで危険性のあるコードはエラーが起きます。
Definite Assignment Assertions 🔗でもありますがエクスクラメーションマーク ! を付けることで意図的にやっていることだと宣言できます。
|
1 2 3 4 5 6 7 8 |
let x: number; console.log(x); //変数 'x' は割り当てられる前に使用されています。ts(2454) //Definite Assignment Assertions let x1!: number; //Non-null assertion operator let x2: number; console.log(x1 + x2!); //NaN |
これを利用してこう書けます。
|
1 2 |
const span = document.querySelector("#p2")!.querySelector("span")!.textContent; console.log(span); |
ただ当然ですがnullだった場合には実行時エラーが出ます。
|
1 2 |
const span = document.querySelector("#NotFoundID")!.querySelector("span")!.textContent; //TypeError: Cannot read property 'querySelector' of null |
Optional chaining 🔗
3.7で使えるようになった比較的新しい機能。いわゆるNull条件演算子。
Null許容型として省略可能なプロパティの定義に使えるクエスチョンマーク ? ですが、Nullableな結果に対するチェーンメソッドにも使えます。
|
1 2 3 4 5 6 7 |
//Optional Chaining const span1 = document.querySelector("#p2")?.querySelector("span")?.textContent; const span2 = document.querySelector("#NotFoundID")?.querySelector("span")?.textContent; const span3 = document.querySelector("#p2")?.querySelector("NotFoundTag")?.textContent; console.log(span1); //span console.log(span2); //undefined console.log(span3); //undefined |
この時のspan1-3はundefinedが追加されたunion型(string | null | undefined)。
今回のケースではこれが一番よさそうです。