【TS】今さら聞けない真偽値
はじめに
今回はタイトルにある通り、「真偽値」つまり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
についてはtrue
かfalse
のいずれかの値を取るので直感的でわかりやすいです。
boolean以外ではどうなる?
では上記のif
の条件の中にboolean
以外の型が入ってきた場合はどうでしょうか?
例えば、flg1
という変数をboolean
ではなくnumber
型で0
か1
の値が入るものとして扱っている場合などです。
// 0:偽, 1:真として扱う
const flg1: number = 1;
if(flg1) {
// 処理①
}
else {
// 処理②
}
既にご存知かと思いますが、この場合も0=false
,1=true
として正常にif
文が処理されます。
では-1
だったら?
あるいはstring
型だったら?
配列やオブジェクト、もしくはnull
やundefiend
だったら?
falseとして扱われる値
このあたりはJavascript
でコーディングされている人の方が知る機会が多いかもしれません。
実は、Typescript
やJavascript
の世界で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
」
④「NaN
もfalse
として扱われます。
逆に言うなら「0
とNaN
以外は-1
だろうがInfinity
だろうが全てtrue
として扱われる」点に注意が必要です。
体感ですが、意図せず分岐をtrue
で通ってしまう不具合は大体number
型の値が絡んでいます。
const nan: number = NaN;
console.log(!!nan); // --> false
null
」
⑤「null
もfalse
として扱われます。
string
の例でも紹介しましたが、「特定のプロパティが存在している(=null
でない)」場合に何かを表示させる時の分岐で使ったりします。
const n: any = null;
console.log(!!n); // --> false
undefined
」
⑥「null
と同じくundefined
もfalse
です。
let u: any;
console.log(!!u); // --> false
これ以外にfalse
と判定される値はありません。
従って空配列[]
や空オブジェクトの{}
もtrue
となります。
Tips
真偽値を扱う場合のさて、false
として扱われる例が分かったと思うので、ここからは私個人がよく利用する記法などのTips
についてご紹介したいと思います。
※以下で紹介する記法は、必ずしも「可読性が高い」とはいえません。しばしば同様の議論を見かけますが、技術レベルの大きく異なるメンバがいるチーム開発の場合は「多少冗長でもif~else構文で記載する」等の注意が必要です。
短絡評価
例えばhoge
というプロパティを持つobj
というインスタンスがあったとして、変数a
にobj.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.hoge
をa
に代入 - もし仮に
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;
この場合、obj
やhoge
を定義しないまま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