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
)。
今回のケースではこれが一番よさそうです。