コーダーに知っておいてもらいたい React JSX の基礎 その4(最終回)

スタッフブログ

Photo by nihon graphy

皆様どうも、こんにちは!
こまりのフロントエンジニア、桑木です。

JSXのタグ中では文(宣言文var等、制御文if等々)が使用できないので、無理やりにでも繰り返しや選択を伴う処理を記述したかったらとして記述するしかない、ということで、前回はJavaScriptの式だけで記述する「繰り返し」について書きました。

そもそも関数を使用すれば無理やり記述する必要はないけれど、簡単な処理であれば式だけで記述されることも多いことから、その手法を知っていこう、ということで、今回はJavaScriptの式だけで記述する「選択」について書いていきます。

そもそも式とはなんだろう?

1 + 1

という式があったとして、+という記号は左の値と右の値を足した結果を返す、という動作をします。この+記号のように値に対して何らかの動作をする記号を演算子(オペレータ)、演算子の対象になる値(この場合はそれぞれの1)を被演算子(オペランド)と言います。また、演算子の動作を実行することを評価すると言います。

式に続きがあれば、評価した部分はその結果に置き換えて評価を続けて、最終的に1つの値が求まるまで評価が続きます。

演算子には優先順位と方向がある

JavaScriptでは演算子に優先順位が決められていて、基本的に式の中で順位の高い演算子から順番に評価されます。(基本的でない場合も後述します)

1 * 3 + 4 / 2

よくある四則演算の演算子を使用した式です。言わずもがなではありますが、この式を評価した結果は5になります。乗算演算子*と除算演算子/の方が加算演算子+よりも優先順位が高いので、このように評価されます。

では、同じ優先順位の演算子が並んだ場合はどうでしょうか?

10 - 5 - 3 - 2

JavaScriptでは演算子ごとに方向も決められていて、-演算子の場合は「左から右」と決められているので式の左側の-演算子から順に評価されることになります。つまり、内部的には((10 - 5) - 3) - 2と評価されます。

もし逆の「右から左」であれば、10 - (5 - (3 - 2))と評価することになってしまいますが、直感に反する方向に決められている演算子はないと考えられるので、方向を意識して式を記述する必要はありません。

また、評価順のわかりにくい複雑な式を記述する場合は、決められている優先順位に関わらずグループ演算子(いわゆるカッコ)( )を使用して読みやすい式を記述するように心がけましょう。

演算子の優先順位 - JavaScript | MDN
こちらにJavaScriptの演算子の一覧、優先順位、方向などが記載されているので参照してみてください。

式で記述する「選択」その1

前置きが長くなりましたが、「選択」とはつまり条件分岐させるということです。条件式を評価した結果が真ならばA、偽ならばBというように、条件によって式を評価した結果の値を変えたいということですね。

JavaScriptには、そのものずばり条件演算子(三項演算子)? :というものが存在します。

y =    x > 0 ? "ウェブ制作" : "システム開発"

?の前に条件式?の直後に条件がの場合の式、:の後に条件がの場合の式を記述します。この条件演算子は、演算子の中でもオペランドを3つ指定する唯一の演算子であることから三項演算子とも呼ばれます。

代入演算子=は右オペランドの値を左オペランドの変数に代入する演算子です。よって、この式は変数xの値が0よりも大きければ文字列ウェブ制作が、0以下ならば文字列システム開発が変数yに代入されることになります。

ちなみに、大なり演算子>や等価演算子==のように左右の値を比較して、その結果として真偽値(truefalseのこと)を返す演算子を総称して比較演算子といいます。

ところで、ここでいう条件式とは何でしょうか?

「条件式」とあえて特別な名前で呼びはするものの、今まで説明していた通常の式とその評価の動作に違いはありません。演算子を順番に評価して最終的に1つの値が求められます。違いとしては、その求められた値が数値や文字列などの真偽値以外でも、それが真trueまたは偽falseのどちらかに判別されるということです。(if文などのカッコ内に記述するものも条件式と言いますが同じ意味です。)

真と判別される値のことをtruthyな値、偽と判別される値のことをfalsyな値とJavaScript界隈では表現します。falsyな値とは、false, 0, 空文字列, null, undefined, NaNの6種類だけで、それ以外の値はすべてtruthyな値です。

// if文の条件式にfalsyな値を指定するとブロックの中は実行されない
if (false) {}
if (0) {}
if ("") {}
if (undefined) {}

// それ以外はすべてtruthyな値なのでブロックの中が実行される
if (true) {}
if (-1) {}
if ("こまり") {}
if ({} /* 空のオブジェクト */) {}

JavaScriptにおける論理演算

前述の比較演算子では、2つの値を比較することしかできません。例えば「変数xが0以上(x >= 0)」とか「変数xが10以下(x <= 10)」などそれぞれの記述は比較演算子を用いることで可能ですが、ではその両方を満たす条件式を記述するにはどうすれば良いのでしょうか。

JavaScriptにはこういった複数の条件式を結合する演算子として、AND演算子&&とOR演算子||が存在します。

一般的な論理学において、AND(論理積)演算とは「すべてのオペランドが真の時は真、そうでなければ偽」、OR(論理和)演算とは「少なくとも1つのオペランドが真の時は真、そうでなければ偽」とする演算のことです。

真偽値Aと真偽値Bに対するAND演算、OR演算の表です。

A B AND(論理積) OR(論理和)
if (0 <= x && x <= 10) {
    // xが0以上、なおかつxが10以下ならばこのブロックが実行される
}

この考えに従えば、AND演算子&&やOR演算子||を評価した結果は比較演算子のように真偽値になりそうなものですが、JavaScriptにおいてはこの演算子を評価した結果はどちらかのオペランドそのままの値になります。

どちらの演算子も左オペランドがfalsyかtruthyのどちらなのかによって、演算子の評価の結果が左右どちらのオペランドの値になるか決まります。AND演算子&&ではfalsyならば左、truthyならば右。OR演算子||はfalsyならば右、truthyならば左、となります。

左オペランド 右オペランド &&演算子 ||演算子
falsy falsy 左(falsy) 右(falsy)
falsy truthy 左(falsy) 右(truthy)
truthy falsy 右(falsy) 左(truthy)
truthy truthy 右(truthy) 左(truthy)

演算子としては左オペランドしか見ていないにも関わらず、結果として真偽(truthy, falsy)の関係が一般的な論理演算と同一になることが上記の表を見比べることでもわかります。

短絡評価

ところで、比較演算子と&&||の演算子3種類の優先順位の関係は、「 || < && < 比較演算子 」となっています。

0 <= x && x <= 10 // 条件式1
0 <= x && x <= 10 && checkFunction(x) // 条件式2 (checkFunctionは処理に時間の掛かる関数)

条件式1において変数x-1だった場合、優先順位の高い比較演算子を先にすべて評価してfalse && falseを最終的に評価するのでしょうか?

前述ようにAND演算子&&は左オペランドがfalsyであれば、右オペランドを評価しなくても結果が左オペランドそのままのfalsyな値になることは明らかです。同様にOR演算子||も左オペランドがtruthyであれば結果は明らかです。

このように右オペランドを評価しなくても結果が求まる場合、JavaScriptでは&&||右オペランドの評価を省略します。この特殊な動作を「短絡評価」といいます。また、この短絡評価は条件演算子? :でも発生します。

条件式2のように時間の掛かる関数呼び出しが含まれている場合、この短絡評価の仕組みはパフォーマンスの向上にとても役立ちます。しかし、いわゆる副作用のある関数(変数の書き換え等を行う関数)の場合、その処理が実行されない可能性があることを意識して条件式を記述する必要があります。

式で記述する「選択」その2

さて、その1で紹介した条件演算子? :はまさに目的に合致する演算子でした。しかし、オペランドを3つも記述する必要があってコードが読みにくくなりやすい演算子です。

そこで条件演算子を使わずに条件分岐させるために、前述の短絡評価をあえて利用する記述がよく使われます。

次のコードは前回の記事に登場したものを改変したものです。

/*
var data = [
    {name: "small", price: 150},
    {name: "medium", price: 270},
    {name: "large", price: 320},
];
*/
function PriceList(data, className) {
    return (
        <div className={ className }>
            <ul>
            {
                data.map( item => <li key={ item.name }>{ `${item.name} ${item.price}円` }</li> )
            }
            </ul>
        </div>
    );
}

この関数は引数がundefinedである可能性や、配列dataが空の可能性があるとしましょう。そして配列が空ならそもそも<ul>要素を出力したくないとします。また、引数classNameが指定されていなければ、文字列normalを代わりに使用したいとします。

そこで&&||を安易に使用することで、それらの対策をすることができます。

function PriceList(data, className) {
    return (
        <div className={ className || "normal" }>
        {
            data && data.length > 0 &&
            <ul>
            {
                data.map( item => <li key={ item.name }>{ `${item.name} ${item.price}円` }</li> )
            }
            </ul>
        }
        </div>
    );
}

className || "normal"では、変数classNameに値があればtruthyなのでそのまま変数の中身になって、undefinedや空文字列であればfalsyなので右オペランドの文字列normalと評価されます。

またdata && data.length > 0 && <ul>...</ul>では、変数dataに値があればtruthyなのでdata.length > 0が評価され、その結果として配列に要素があればtruthyなので、最終的には<ul>要素が出力されます。

変数dataundefinedだったり配列に要素がなければ、式全体の評価としてはundefinedfalseになります。前回の記事でも説明したようにReactでこれらの値はなにも出力されないことが仕様で決められています。よって、変数dataが空であれば<ul>要素は出力されませんし、undefinedなどが文字列として出力されることもありません。

そもそも、この短絡評価を利用した記述を使えるようにするために、Reactの仕様がこのように決まっていると思われます。

ひとまずはこれくらいで

全4回にもなる記事を書いてきましたが、コーダーとしてデザイン面でJSXを触るくらいなら、このくらい知っておけば大体のことは分かるのではないでしょうか。

ただし、式だけで繰り返しや選択を記述する方法はとても便利だし、JavaScriptでは本当によく使われるテクニックなのですが、やりすぎるととても読みづらいコードになってしまいます。関数や変数に分けるなどの他のより適切な方法がないかも考えるようにして、トータルで読みやすいコードを記述するように心がけてください。