🐝

【TS】今さら聞けない真偽値

2020/11/05に公開

はじめに

今回はタイトルにある通り、「真偽値」つまりbooleanについての記事です。
真偽値を全く使わずにコーディングをしている人は恐らくいないと思うので、日常のどこかしらでは触れているのではないかと思います。

しかしながら、その内容について細かく把握されている方は案外少ないのではないかと思ったので記事にしました。

よく使うであろう真偽値

大体が以下のような使い方をしているのではないでしょうか?

const flg1: boolean = true;
const flg2: boolean = false;
const flg3: boolean = true;

// if...elseの判定に使う
if(flg1) {
  // 処理①
}
else {
  // 処理②
}

// &&や ||オペレータと組み合わせて使う
if(flg1 && flg2) {
  // 処理③
}

if(flg2 || flg3) {
  // 処理④
}

あるいは三項演算子を使って変数に値を代入する場合などでしょうか。

const flg1: boolean = true;
const content: string = flg1? 'hogehoge' : 'fugafuga';

こういった使い方が恐らくメジャーで、どんな入門書や講座資料を見ても上記のようなコードを目にされるかと思います。
booleanについてはtruefalseのいずれかの値を取るので直感的でわかりやすいです。

boolean以外ではどうなる?

では上記のifの条件の中にboolean以外の型が入ってきた場合はどうでしょうか?

例えば、flg1という変数をbooleanではなくnumber型で01の値が入るものとして扱っている場合などです。

// 0:偽, 1:真として扱う
const flg1: number = 1;

if(flg1) {
  // 処理①
}
else {
  // 処理②
}

既にご存知かと思いますが、この場合も0=false,1=trueとして正常にif文が処理されます。
では-1だったら?
あるいはstring型だったら?
配列やオブジェクト、もしくはnullundefiendだったら?

falseとして扱われる値

このあたりはJavascriptでコーディングされている人の方が知る機会が多いかもしれません。
実は、TypescriptJavascriptの世界でboolean型のfalseとして処理される値は 「6種類」 しかありません。

以下、簡単に6種類の値を紹介していきます。

①「boolean型のfalse

一番オーソドックスな値です。

const flg: boolean = false;
console.log(!!flg); // --> false

②「string型の空文字""

これも割と有名です。
Web系システムだと、ラベルとして表示する文字列が存在する場合のみ要素として追加する、といった場合に用いられることが多いです。

const lbl: string = '';
console.log(!!lbl); // --> false

③「number型の0

先に紹介した通りです。

const num: number = 0;
console.log(!!num); // --> false

④「number型のNaN

NaNfalseとして扱われます。
逆に言うなら「0NaN以外は-1だろうがInfinityだろうが全てtrueとして扱われる」点に注意が必要です。
体感ですが、意図せず分岐をtrueで通ってしまう不具合は大体number型の値が絡んでいます。

const nan: number = NaN;
console.log(!!nan); // --> false

⑤「null

nullfalseとして扱われます。
stringの例でも紹介しましたが、「特定のプロパティが存在している(=nullでない)」場合に何かを表示させる時の分岐で使ったりします。

const n: any = null;
console.log(!!n); // --> false

⑥「undefined

nullと同じくundefinedfalseです。

let u: any;
console.log(!!u); // --> false

これ以外にfalseと判定される値はありません。
従って空配列[]や空オブジェクトの{}trueとなります。

真偽値を扱う場合のTips

さて、falseとして扱われる例が分かったと思うので、ここからは私個人がよく利用する記法などのTipsについてご紹介したいと思います。

※以下で紹介する記法は、必ずしも「可読性が高い」とはいえません。しばしば同様の議論を見かけますが、技術レベルの大きく異なるメンバがいるチーム開発の場合は「多少冗長でもif~else構文で記載する」等の注意が必要です。

短絡評価

例えばhogeというプロパティを持つobjというインスタンスがあったとして、変数aobj.hogeの値があれば代入し、なければ"null"という文字列を入れたい場合を想定します。

素直に記載すると以下のようになります。

const obj : {hoge: string} = {
  hoge : 'hogehoge'
};

let a: string = '';
if(obj.hoge) {
  a = obj.hoge;
}
else {
  a = 'null';
}

やりたい事の割になんだかステップ数が嵩んでいるような気がします。
そこで 「短絡評価」 の出番です。

const obj : {hoge: string} = {
  hoge : 'hogehoge'
};

let a: string = obj.hoge || 'null';

||if文内でよく使うため「左右どちらかの値がtrueならtrueを返す」ように思われがちですが、
実は 「左辺を評価してtrueなら左辺を、falseなら右辺を返す」 のが正しいです。

従って上記の例では

  • 左辺のobj.hogeを評価
  • trueなので左辺のobj.hogeaに代入
  • もし仮にfalseだったら右辺の"null"aに代入

という挙動になります。

&&についても同様のことが言えます。
&&「左辺を評価してtrueなら右辺を、falseなら左辺を返す」 という挙動です。

const obj : {hoge: string} = {
  hoge : 'hogehoge'
};

// この場合左辺obj.hogeが`true`なので右辺の"null"がaに代入される
let a: string = obj.hoge && 'null';

オプショナルチェイニング演算子

上記の例で仮にobjそのものや、プロパティhogeが必須でない場合はどうでしょうか?

const obj : {hoge?: string} | undefined;

この場合、objhogeを定義しないままobj.hogeにアクセスしようとすると下記のエラーが出ます。

console.log(obj.hoge);
// --> Uncaught TypeError: Cannot read property 'hoge' of undefined

hogeはおろかobjも定義されていないためエラーが出るのは当然かと思います。
例えばサーバサイドから取得してきたデータも同様で、確実に値やプロパティ が取得できている保証がない場合はアクセスする前に「存在しているかどうかのチェック」をすると思います。

let obj : {hoge?: string} | undefined;

let a: string = '';

// アクセスしたいプロパティまでが全て定義されているかチェック
if(obj && obj.hoge) {
  a = obj.hoge;
}

obj.hogeという比較的浅い階層なので問題ないですが、例えばこれがobj.hoge.fuga.puniのようにプロパティが入れ子になっていくにつれ、判定も長くなっていきます。

let obj : {hoge?: { fuga?: { puni: string } }} | undefined;

let a: string = '';

// アクセスしたいプロパティまでが全て定義されているかチェック
if(obj && obj.hoge && obj.hoge.fuga && obj.hoge.fuga.puni) {
  a = obj.hoge.fuga.puni;
}

こうして見ると、なんだか冗長な気がします。
そこで活躍するのがオプショナルチェイニング演算子?.です。

これは指定したプロパティに到達するまでの参照が正しいかどうかをチェックせずにアクセスし、正常にアクセスした場合はその値を、できない場合はundefinedを返します。

let obj : {hoge?: { fuga?: { puni?: string, baz?: string } }} | undefined = { hoge : { fuga : { baz: 'aaa' } } };

let puni: string = obj?.hoge?.fuga?.puni;
let baz: string = obj?.hoge?.fuga?.baz;

// puniは未定義のためundefinedが入る(ここでエラーが出て落ちることはない)
console.log(puni); // --> undefined
// bazは存在するため通常通り代入される
console.log(baz); // --> "aaa"

ポイントは先ほどのように対象プロパティまでの参照が存在するかをチェックする必要がないということです。
先の短絡評価と合わせて次のように書くこともできます。

// obj.hoge.fuga.puniが参照できなければ"null"が入る
let puni: string = obj?.hoge?.fuga?.puni || 'null';

また、関数に対しても利用できます。

console.log(obj?.hoge?.fugaFunction?.()); // --> undefined

まとめ

今回は真偽値と、それに関するTipsをいくつか紹介しました。
真偽知やそれに関する処理は、なんとなく知っている状態でもある程度動作してくれるので、何かしらのバグが発生して細かく調べた時にようやく理解することが多いように思えます。
この記事の内容が少しでも役立てば幸いです。

Discussion