👻

JSの論理演算子と三項演算子で事故った

2020/11/18に公開
4

結論から言えば演算子いっぱい使った式書くなら()つけとけって話なんですが、仕事でReact書いてる時に論理演算子と三項演算子を何も考えずに使っていて挙動が意味不明になったので調べてみました。

問題になったコード

bool1 && bool2 ? "trueコンポーネント" : "falseコンポーネント"
このコードの期待する結果としてbool1がtrueの時だけ表示、かつbool2でコンポーネントを出し分ける的なものを想定しており、そのために論理演算子と三項演算子つかって書いていました。
まぁ何も考えずに書いていたので、案の定レビューの時、分かりづらみというコメントをいただきました。🙇‍♂️
今思うと確かにクソわかりにくいコードを書いてますし、このコードだとbool1がfalseだと本来はどちらのコンポーネントも表示したくないのですが"falseコンポーネント"を表示してしまうので意図しないバグを産んでたのでレビューで救われました笑(適当に書くのはよくないよ⭐️)

この場合、自分が意図した挙動で表示するなら
bool1 && (bool2 ? "trueコンポーネント" : "falseコンポーネント")
とすればbool1がfalseの場合は表示しない様にできてbool2で表示するコンポーネントを分岐できます。

それか

bool1 && (
 <>
    {bool2 ? (
       "trueコンポーネント"
    ) : (
       "falseコンポーネント"
    )
  </>
)

って感じで分けた方がまだわかりやすいかもです。
とりあえず括弧つけとけばなんとかなります。
他にもやり方はあると思いますが、今回の話的には括弧付ければオッケーです。


話は終わりましたが、
あれ?括弧ついてない時ってどう評価されるのかな?ってちょっと興味が沸いたのでみていきます。

考えてみる

とりあえず問題となったコードを考えていきたいと思います。
しかし正直論理演算子と三項演算子どちらが先に評価されるのかわからんので、やっぱり考えるのをやめて全部の場合を書いて試してみます。

結果は

1.bool1 bool2共にtrue
→"trueコンポーネント"

2.bool1 true, bool2 false
→"falseコンポーネント"

3.bool1 false, bool2 true
→"falseコンポーネント"

4.bool1 bool2共にfalse
→"falseコンポーネント"

結果からみるにbool1がfalse、bool2がtrueでも"falseコンポーネント"が返ってくるということは、bool1で式が終わっておらず、bool2のtrueによって三項演算子が分岐していないので、
(bool1 && bool2) ? "trueコンポーネント" : "falseコンポーネント"
となってfalseコンポーネントが返されていると推測できます。

このことからあの状態のままだとbool1がfalseでもコンポーネントが表示されてしまい意図しない挙動となっていたと言えます。(怖いね👍)

ここら辺ちゃんと調べてみたいけどどうやって調べたら良いのかわからん。切に教えていただきたい。

論理演算子について余談

今回の修正で括弧をつけて
bool1 && (bool2 ? "trueコンポーネント" : "falseコンポーネント")
と書けば意図して動くやーんと思っていたのですが、よくよく考えてみると三項演算子で返ってきた結果は文字列だけどなんで結果として文字列を返すの??文字列返すって真偽値としてどうなってるの??意味不明😇

と挙動が謎だったので発狂しながら調べてみました。

とりあえずjs書いてみます。

実践卍

まずはシンプルに論理演算子の挙動をみたいのでbool型で試してみる。
true && false
→false
true && true
→true

ここら辺は想定内。
では文字列も含めてやってみます。

true && "false"
→"false"

true && ""
→""

false && "true"
→短絡評価でfalse

文字列を含めると文字列が返ってくる様になりました。
最後の部分に関しては論理演算子の短絡評価で最初がfalseなら後ろは評価しないやつ。その結果文字列trueは表示されずbool型のfalseが返ってきました。

bool型ではなかったらその値を返すみたいですね。。🤔
試しに数字でもやってみました
true && 1
→1
やはり真偽値でないものを返している様です。

式全体だとtrueと見なすのかな謎なので、if文に突っ込んでみました。

const isTrue = true && "";
if (isTrue) {
  console.log(true);
} else {
  console.log(false);
}

結果はfalse
と返ってきたので一応空文字""式的にはfalseの模様。

const isTrue = true && "trueになって欲しい";
if (isTrue) {
  console.log(true);
} else {
  console.log(false);
}

結果はtrue
文字列を入れるとtrueになった。

ということは空文字""は一応真偽値としてはfalseとみなされている。その結果
true && ""
これは式的にはfalseで、評価値は""ということになりました。
この場合論理演算子はbool型の評価値を返すのではなく、文字列を含む場合は評価値は文字列になるということなのか🤔

空文字はfalseとみなしていそうなので最初に持ってきてみる。
"" && false
→""
空文字なので式はfalse、評価値は""が返ってきて評価値が文字列なら文字列を返していそう。
ちなみに全部試すのめんどくさいですが、||だと
"" || false
→false
が返ってきます。
このことから、
最後に評価したものを評価値として返すが、それが真偽値以外ならそいつ自身を返す
と言えそうです。なので"" && falseで空文字""が返ってきたのは短絡評価で""の時点でfalseとみなされ後ろのfalseは評価されなかった結果、評価値を返す時にfalseではなく空文字の""が返されるのだと思います。

||を使うとさらにややこしくなりそうです。すでに理解が追いついていなくて調べたくないので、各自調べてもらえれば幸いミーです。

また下記の様にすると
"true" && "truetrue"
→"truetrue"
これは最初の部分に文字列"true"が入ってることでtrueとして扱われて、次に後の方が評価される。後ろの文字列"truetrue"はtrueなので式全体的にはtrue、そして最後が文字列なのでその文字列を評価値として返して"truetrue"となる。
やはり最後に評価したものを評価値として返していそうです。

そもそもJSちょっと特殊だった

JS以外の言語って論理演算子使う時真偽値しかとってくれなくて、文字列を真偽値として取ってくれない。自分はjsから勉強したから全部できるものだと思ってた。
全ての言語がそうかってわけではないと思うけどめんどいので調べてない。
phpとかだと文字列を取ってはくれるけど評価値は真偽値になってるっぽい。goとかだとそもそも真偽値同士でしか使えない。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Logical_Operators

&& および || 演算子は真偽値ではない値も使うことができるため、その場合は、真偽値ではない値を返すことがあります。

書いてあるやーんって感じ。

最後に

まぁとりあえず括弧つけようと思います。
こんなコード二度と書かないとここに誓います。✌️

Discussion

standard softwarestandard software

問題は括弧ではなくてですね....
https://qr.ae/Tm2RNb

あるいは

(bool1 && bool2) ? "trueコンポーネント" : "falseコンポーネント"

こちらでしょう。

ん?

このコードの期待する結果としてbool1がtrueの時だけ表示、かつbool2でコンポーネントを出し分ける的なものを想定しており、

ああ、違うのか、、確かに読みづらい。

bool1 === false ? null 
: bool2 ? "trueコンポーネント" : "falseコンポーネント"

True属性/False属性を使ったコードは「==」が悪なのと同じく悪なので、避けた方がよいです。

はるかはるか

url拝見しました
自分はいきって死んでたタイプですね精進します😇

問題は括弧ではなくてですね....

括弧つけるだけで解決するわ!みたいな書き方で書いてしまったのも悪かったですね。確かにバグが分かりづらくなる可能性があるのには変わりないと思います。ただ今回、その点以外に気になったので書いたって感じですね。

ああ、違うのか、、確かに読みづらい。

自分の文章力を呪いたくなりますね。

期待する処理として

(bool1 && bool2) ? "trueコンポーネント" : "falseコンポーネント"

ではなく

bool1 && (bool2 ? "trueコンポーネント" : "falseコンポーネント")

になって欲しかったが、

bool1 && bool2 ? "trueコンポーネント" : "falseコンポーネント"

と書いてしまったことで

(bool1 && bool2) ? "trueコンポーネント" : "falseコンポーネント"

と同義になってしまってたって感じですね。
正直同義なのかは内部までみてないので、推測でしかないですが。
ちょっと気になるところです。

standard softwarestandard software

ぶっきらぼうなコメントに、返信ありがとうございます。

&& で連続させてコンポーネント表示させるやつ、これ、もう業界に蔓延してますからねー。誰か他の書いた人のマネをしていったんだと思うので他の人を恨みましょう。

短略評価じゃなくて、短絡評価っていうみたいですよ。
読みづらいというのはコードの方ですので、文章は丁寧で臨場感あって素敵です。

あと、このあたりもご参考にどぞ。でも演算子の優先度を知らないと読めないコード書いちゃだめなので、たいていつねに括弧多めがおすすめです。

演算子の優先順位 - JavaScript | MDN
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Operator_Precedence

はるかはるか

優しいお言葉ありがとうございます☺️

他の人を恨みましょう。

短絡評価っていうみたいですよ

恥ずかしい。。。さりげなく書き換えときます。

ありがとうございます。確かに優先度知っとけよってなると大変だし覚えるのもめんどくさいのでやはり分かりやすいを徹底して括弧つけてあげる方が親切ですね🤔