プログラミング言語

【TypeScript】TypeSriptにおけるnull・undefinedチェックについて

【TypeScript】TypeSriptにおけるnull・undefinedチェックについて

目次

  1. strictNullChecks
  2. Null合体演算子とNull合体代入演算子
  3. Optional Chaining
  4. Assertion Functions
  5. 最後に

今回は、TypeScriptにおけるnull・undefinedチェックについて様々な機能を紹介していこうと思います。

プログラミングの穴を無くすために、nullやundefinedのチェックは必須です。

型に厳格なTypeScriptではnullやundefinedのチェックを厳しくする機能や楽にする機能が多数用意されています。

なので、TypeScriptでの開発の際にはしっかりとnullチェックをするように意識しましょう。

これまでTypeScriptに関する記事をいくつかあげてきたので、そちらもあわせてご覧ください。

【TypeScript】TypeScriptとは?概要やメリット、環境構築からHelloWorld表示
https://www.dailyupblog.com/languages/1680/

【TypeScript】基本構文と基本型、interfaceやType Aliasまでをご紹介
https://www.dailyupblog.com/languages/1717/

前回:【TypeScript】Union TypesとGenericsの使い方をご紹介 〜型操作応用編〜
https://www.dailyupblog.com/languages/1741/

それではいきましょう。

strictNullChecks

通常のJavaScriptであれば、型チェックの概念が存在しないため、文字列が代入されている変数にnullやundefinedが代入されても特にエラーにはなりません。

ですが、TypeScriptにおいてはnullやundefinedも一つの型であるため、string型の変数にnullやundefinedを代入したらエラーになります。

//JavaScriptの場合
let str = "hoge";
str = undefined //エラーにならない

//TypeScriptの場合
let str: string = "hoge";
str = undefined //エラーになる

ただ、TypeScriptでもデフォルトではエラーにはなりません。

tsconfig.jsonでnull・undefinedがチェックされるように設定する必要があります。

下記のように「strictNullChecks」をtrueにします。デフォルトはfalseです。

//・・・省略
"strictNullChecks": true,
//・・・省略

この設定にすることで、null・undefinedを別の型の変数に代入するとエラーになります。

Null合体演算子とNull合体代入演算子

値にnullもしくはundefinedが代入される可能性がある場合、値の後に「??」でデフォルト値を記述することでもしnullやundefinedが代入された場合、デフォルト値の方が代入されるようになる機能です。

これをNull合体演算子といいます。

let message = null ?? "こんにちは";

console.log(message); //こんにちは

これまではif文で分岐を書くことがあったと思いますが、この書き方をすればここまですっきりと見やすくなります。

もう一つより省略して記述できるのが、Null合体代入演算子です。

イコールを「??=」と記述することで変数にnullもしくはundefinedが代入された場合に、後ろの値をデフォルト値として代入する機能です。

先ほどの例をNull合体代入演算子で書くと下記のようになります。これは先ほどの例と同義です。

let message = null;

message ??= "こんにちは";

console.log(message); //こんにちは

このようにより省略して書くことができます。

Optional Chaining

Optional Chaining(オプショナルチェーン)とは、オブチェクトチェーンの中にあるプロパティの値がnullもしくはundefinedだった場合、エラーにせずnull・undefinedを返して、処理を終了する演算子です。

例えば下記のような例です。

interface Book {
	title: string,
	info?: {
		author?: string
	}
}

const bookIntro = (book: Book): string => {
	return `本のタイトル:${book.title}\n本の作者:${book.info.author}`;
}

let book01 = {
	title: "hoge"
}

console.log(bookIntro(book01));

上記の例は、本の情報に関するBook型の値を引数として受け取り、本の情報を返す関数を実行しているといったものです。

一見特に問題のない例のように見えますが、これをこのまま実行するとエラーが出ます。

上記のように「book.info.author」の値は、Book型の中身を見てみると、「info」や「author」はオプショナルなプロパティ(任意値)であるため、nullもしくはundefinedの可能性があります。

変数「book01」はtitleプロパティしか値が設定されておらず、info.authorは設定されていないです。

staticNullChecksがtrueになっているため、null・undefinedの可能性があるのでエラーになります。

ただ、このエラーを回避するためにご紹介する「Optional Chaining」を使ってみると下記のようになります。

interface Book {
	title: string,
	info ?: {
		author?: string
	}
}

const bookIntro = (book: Book): string => {
	return `本のタイトル:${book.title}\n本の作者:${book.info?.author}`;
}

let book01 = {
	title: "hoge"
}

console.log(bookIntro(book01));
//
本のタイトル:hoge
本の作者:undefined

「book.info?.author」のように「?.」演算子で繋げると、先ほどのエラーを回避できます。

Assertion Functions

Assertion Functionsは、データがnullでないことを担保したい場合に、nullチェックを関数としてすっきり書くことができるものです。

例えば、下記の例を見てみます。

interface Member {
	id: number,
	name: string
}

const getName = (members: Array<Member>, id: number): string => {
	const target = members.find(member => member.id === id);
	return target.name;
}

getName関数は引数でMember型の値を持つ配列を受け取り、第二引数のidと比較して、nameを返すといったものです。

一見問題ないように見えるこのコードですが、TypeScriptでは、このコードはエラー扱いになります。

find関数は配列の中から条件にあった値を返すものですが、nullやundefinedの値を返す可能性があるため、エラーとなります。

なので、nullチェックをしなければならないので、下記のように書けばエラーは解消されるでしょう。

interface Member {
	id: number,
	name: string
}

const getName = (members: Array<Member>, id: number): string => {
	const target = members.find(member => member.id === id);
	if(target == null) {
		throw new Error("this path must not be reached");
	}
	return target.name;
}

ただ、この書き方だと毎回if文を使ってnullチェック処理を書かなければならず、見通しの悪いコードになってしまいます。

そういった場合に、Assertion Functionsを使えばすっきりとした見やすいコードになります。

先ほどのコードをAssertion Functionsで書くと下記のようになります。

interface Member {
	id: number,
	name: string
}

function assertIsDefined<T> (value: T): asserts value is NonNullable<T> {
	if(value === undefined || value === null) {
		throw new Error(`Expected 'value' to be defined, but received ${value}`);
	}
}

const getName = (members: Array<Member>, id: number): string => {
	const target = members.find(member => member.id === id);
	assertIsDefined(target);
	return target.name;
}

上記のように、assertIsDefined関数を定義し、この関数を使用して、nullチェックを行っています。

if文の例と比べると、見やすくなります。

また、上記の書き方を汎用化したassert関数というのも存在します。

function assert (condition: any, message?: string): asserts condition {
	if(!condition) {
		throw new Error(message);
	}
}

const area = (length: number, width: number) => {
	assert(0 < length);
	assert(0 < width);
	return length * width;
}

この書き方はデータが特定の条件を満たす必要があることを明示する必要があります。

最後に

今回は、TypeScriptにおけるnull・undefinedチェックに関する様々な機能をご紹介しました。

ルールに厳格なTypeScriptだからこそ、nullチェックも欠かさず行うようにしましょう。

前回の記事:【TypeScript】Union TypesとGenericsの使い方をご紹介 〜型操作応用編〜
https://www.dailyupblog.com/languages/1741/

次回の記事:【TypeScript】TypeScriptにおけるオブジェクト指向構文
https://www.dailyupblog.com/languages/1801/

関連記事

関連記事