TypeScriptでの「!」ってなんなん?

2021/06/13に公開

はじめに

開発しているといろんな場所に「!」やら「?」やらが出てきます。
使われる箇所によって当然大きく意味合いが変わってきます。理解できていないまま他のコードを見てなんとなく使っていたので、今回は「!」について整理してみました。

等しくない!...の「!」

不等価演算子。2つのオペランドが等しくないかをチェックし、ブール値の結果を返します。
左右の値が等しくない場合にtrueを、等しい場合はfalseを返す演算子です。

const num = 10;

console.log(1 != 1)          // false (「1と1って等しくないよね?」 => NO)
console.log(num*5 != 50)     // false (「10x5と50って等しくないよね?」 => NO)

否定します!...の「!」

論理否定演算子。真値をとれば偽値になり、偽値をとれば真値になる。

const isVisible = true;

console.log(isVisible);       // true
console.log(!isVisible);      // false (!を前につけて否定する。)

上記のように!が付けられたBool値がtrue => falseに、false => trueにする。と思っていたのですがその理解では十分ではないようです。

というのが、正しい理解です。
型キャストというのは、文字列や数値だとしてもBooleanが評価される文脈(たとえばif()の中とか)ではBooleanとして評価される。ということです。

二重否定「!!」

二重否定なんかもあります。!!true = !false = trueです。ぱっと見完全に意味のないコードですよね。ですが、Falsy/Truthyが絡んでくると話は違ってきます・・・

const name = "jojo"               // 文字列
console.log(name)                 // jojo

const isNotEmpty = !!name         // Truthy(name) => false(!name) => true(!!name) 
console.log(isNotEmpty)           // true 
  • "jojo" : 文字列(Truthy)
  • false : !をつけることで型キャストを行ってその値を反転してfalseにする。
  • true : さらに!をつけてtrueにする。

といった流れで値を変化させ、undefinedかどうか、0かどうか、nullかどうかなどをbooleanの値に変換できる便利なやつです。もちろん文字列以外にも配列などでも同じようなことができます。

Falsy/Truthyな値

  • Falsyな値は全部で8つです。(falseundefinednullNaN"",0-00n)
  • Truthyな値は上記のfalsyな値以外の全てです。
console.log(false)             // false 
console.log(!false)            // true (false => true に反転)
console.log("false")           // true (文字列"false"は truthy)
console.log(!"false")          // false (文字列"false"を truthy => false に反転)
console.log(0 == false)        // true (0はfalsyな値なので、false == false)

nullじゃない!...の「!」

Non-null assertion operator。OptionalParamaterがnon-nullであるということをコンパイラに明示する。オブジェクトのプロパティに後置で!をつけることで、nullである可能性を排除して処理を進めることができます。

 type Name = {
  firstName : string;                // firstName は必須
  lastName? : string;                // lastName はあっても無くてもOK
 }

 const name = {
   firstName : "Joseph",
   lastNamae : "Joestar",
 }

 const isJojo = (name:Name):boolean => {
   // firstNameはstring型ということが確定してるからエラーは出ない。
   const first = userName.firstName.slice(0,2)
  
   // lastNameはstring型であるとは限らない(undefinedの可能性がある)から・・・
-  const last = userName.lastName.slice(0,2)       // undefinedの可能性があるのでエラーが出る。
+  const last = userName.lastName!.slice(0,2)      // !をつけてundefinedではないことを宣言するとエラーがなくなる。 
 
   return first + last === "JoJo"
 }

上記の例をみてみると、
Nameの型において、lastNameプロパティはオプショナルであり、undefinedの可能性があります。つまりlastNameの型はstring | undefinedということです。そのような状態では、

  • stringに対してslice()を正常に呼び出すことはできますが、
  • undefinedに対してslice()を呼ぶと、実行時エラーになってしまいます。

それを避けるためにTypeScriptは気をきかせて型エラーを出してくれます。
ただ、「その値は型的にはnull/undefinedな可能性はあるけど、今はnullでもundefinedでもないんだよ。だからエラーなんて出してくれるな。」と思った時は上記のように、

const last = userName.lastName!.slice(0.2)

と書くことで、TypeScriptは、undefinedである可能性を無視し、string型として扱ってくれるようになります。

さいごに

とても基本的なことなのだとは思いますが、Falsy/Truthyとか、non-Nullだとかは、頭の中が整理できてよかったと思います。次は「?」についてまとめるような気がします。

Discussion