JavaScriptとの戦いの大部分は、undefinedとの戦いです。undefinedはあなたのプログラムに素早く忍び込み、全てを壊していくでしょう。
これはTypeScriptでも避けられません。undefinedになりうる値があれば、繊細なガラス細工に触れるときのように慎重に扱わなければなりません。
そんなundefinedとの戦いの中、ほんのちょっぴり役に立つ文法がJavaScriptに導入されたので、紹介したいと思います。
Optionalな値
コードを書いていると、「入力してもしなくても良い」値を扱いたくなる場合があります。例えば外部からデータをフェッチしてくる関数を作るときには、trueかfalseか未指定(undefined)を指定する「disableCache(キャッシュ無効化)」フラグが欲しくなるかもしれません。
そういったoptional(任意入力)の値を実現しようとすると、コード上ではなかなか煩雑になります。
以下のような関数を書いた経験が、誰しも一度はあるでしょう。
function doSomething(options = {}) {
// 引数からパラメータを取り出す
const paramA = options.paramA || 'default';
// 何かの処理をする
}
この関数 doSomething
は引数 options
の中の paramA
というプロパティの値を使用して処理を進めます。ここで paramA
は任意入力の値であり、中身があるとは限りません。そのためOR演算子でデフォルト値を結合し、未入力(undefined)の場合はデフォルト値が適用されるようにしています。
実は上のコードは少し不適切で、 paramA
が空文字列の場合にもデフォルト値がセットされてしまい上手く動きません。より適切に書くなら、一例として以下のようになります。
function doSomething(options = {}) {
const paramA = 'paramA' in options ? options.paramA : 'default';
// 何かの処理をする
}
長くなってきましたね。今回はプロパティがひとつだけですが、現実にはもっとたくさんの任意入力プロパティが存在すると思います。そうなってくると、プロパティの数だけ同じチェックをしなければならず、コードがだんだんと荒れてきます。
ここで paramA
がオブジェクトだったときのことを考えましょう。コードはさらに複雑になります。
function doSomething(options = {}) {
const paramChild = options.paramA ?
'paramChild' in options.paramA ?
options.paramA.paramChild :
'default' :
'default';
// 何かの処理をする
}
おやおやおや。私にはもうこのコードは読めません。何が書いてあるのかさっぱりです。お手上げです。
このように、任意入力の値というのは扱いが簡単ではありません。エラーを防ぐために注意深くコードを書く必要があり、複雑化を招きます。
Optional Chaining
我々は、任意入力プロパティにおけるundefinedとうまく戦っていかなければなりません。また、任意入力のプロパティの他に、どこかのサービスのAPIから取得したJSONデータには「入っているかもしれないし入っていないかもしれない」キーが存在するかもしれません。
最近になって、そういったoptional(任意入力)を簡単に扱うことができる「Optional Chaining」という機能がJavaScriptに導入されたので紹介したいと思います。
Optional Chainingはピリオドのかわりに ?.
で接続し、 a?.b
のように使います。
const obj = { param: 'Hello' };
const len = obj.param?.length; // 5
const udf = obj.paramB?.length; // undefined
a.b
のかわりに a?.b
と書くと、aの値によって挙動が変わります。aが値を持つ場合、ピリオドと同じようにプロパティにアクセスできます。一方でaがnullまたはundefinedの場合、そこで評価は打ち切られ値がundefinedとなります。従来ならばif文や三項演算子を用いて複雑な処理をしていた部分が、たった1行ですっきり書けます。
nullやundefinedが見つかった段階で強制的に打ち切られるので、あの憎い「Cannot find property 'hogehoge' of undefined」を見ることもありません。Optional Chainingを使うことで、「存在するかもしれない(存在しない可能性もある)」プロパティに安全にアクセスすることができます。
例えば以下のコードはエラーを出さずに普通に動きます。
const val = null?.length; // undefined
?.
を使ってアクセスしているのでnullの段階で打ち切られて値はundefinedになります(Optional Chainingを使うとnullの場合でも強制的にundefinedになります)。
Optional Chainingは連続して使ったり途中だけ使ったりできます。
const udf = undefined;
const obj = {
paramB: 'Hello'
};
// udfがundefinedなので `udf?` の時点で打ち切られ
// val1はundefinedとなる
const val1 = udf?.prop?.propB;
// obj.paramAは存在しない(undefined)なので
// `obj.paramA?` の時点で打ち切られval2はundefinedになる
const val2 = obj.paramA?.paramChild;
// obj.paramBは存在するため先へすすみ、
// val3にはlengthの値が入る
const val3 = obj.paramB?.length;
Optional ChainingはChrome、Firefoxで既に利用可能です。SafariおよびNode.jsは2020年03月24日時点で未対応ですが、そう遠くないうちにサポートするでしょう。TypeScriptではバージョン3.7から利用可能です。
Nullish Coalescing Operator
nullやundefinedとの戦いにおいて、「nullかundefinedであればデフォルト値をセットする」という処理を行うことがあります。
function doSomethind(options = {}) {
const param = options.paramA?.paramChild || 'default';
}
上のように書きたくなりますが、これはうまく動作しません。JavaScriptではTruthy(trueっぽい)/Falsy(falseっぽい)という概念が存在し、数値のゼロや空文字列もfalseとして扱われます。そのため例えばparamChildに空文字列を設定しても'default'という文字列で上書きされてしまいます。このコードを正常に動作させるためには、もっと回りくどい書き方が必要になります。
Truthy/Falsyを判定するのではなく、「nullまたはundefinedであるか」を判定する演算子があればこれは解決できそうです。
そこで現れたのが「Nullish Coalescing Operator」です。Nullish Coalescing Operatorでは ||
のかわりに ??
と書きます。
function doSomethind(options = {}) {
const param = options.paramA?.paramChild ?? 'default';
}
||
は「左の値がFalsyのとき右の値を採用する」ですが、??
は「左の値がnullかundefinedのときのみ右の値を採用」という挙動になっています。そのため左の値がゼロや空文字列の場合でも無視されず、的確にnullとundefinedのみを潰すことができます。
const a = 0 || 'Hello'; // 'Hello' になる
const b = 0 ?? 'Hello'; // 0 になる
Nullish Coalescing Operatorは2020年03月24日時点で、Chrome、Firefoxが対応しています。
Optional Chainingの高度な使い方
Optional Chainingではプロパティへのアクセスだけでなく、配列のインデックスアクセスや関数呼び出しに使用することもできます。
配列へのアクセスは ?.[i]
を用います。 a?.[0]
と書けば、aがnullでもundefinedでもない場合のみ値を取得し、そうでない場合は評価を打ち切ってundefinedとなります。
以下のようにします。
const udf = undefined;
const array = [1, 2, 3];
console.log(udf?.[1]); // undefined
console.log(array?.[1]); // 2
関数呼び出しでも使用できます。関数呼び出しでは ?.(args)
を使用します。
const func1 = undefined;
const func2 = () => console.log('Hello');
func1?.(); // undefinedなので実行されない
func2?.(); // 実行される
なお、配列についても関数についてもちょっと記法が気持ち悪いなと感じたかもしれません。これはJavaScript実行エンジンが func?()
を解釈するよりも func?.()
を解釈する方が楽だったという事情があり、このような文法に決まりました。
さいごに
JavaScriptではoptionalな値を扱う場面が多く存在します。nullやundefinedと戦うための道具が整えられていくのは必然と言えるでしょう。
Optional ChainingもNullish Coalescing Operatorも、どちらも非常に便利で強力な機能です。機会があれば積極的に使っていきましょう。まだ対応していない環境もあるため無条件には使えませんが、心強い味方になってくれるはずです。