React Native の JSX 内で && 演算子を使ったらエラーに怒られた
始め
先月から React Native を使うプロジェクトに参加することになって頑張って取り組んでます。が、ウェブと違う部分で戸惑ったことがありましたので記事にしたいと思います。
1. 発端
API から持ってきたイメージの source Url をコンポーネントに使おうとしたときでした。
<View>
{imageUrl && <Image source={{ uri: imageUrl }} />}
</View>
そうしたらなぜか以下のエラーで怒られたのです。
Invariant Violation: Text strings must be rendered within a Text component
ウェブだったら問題ないはずですが、RNではだめなようです。
2. 原因
まずは原因を探しました。
2-1. React Native
ウェブだったら文字列をdiv
タグとかに直接入れてもなんの問題もないです。
<p>文字列が入れます</p> // OK
<div>文字列が入れます</div> // OK
<span>文字列が入れます</span> // OK
ですが、React Native では文字列は必ずTextコンポーネントの中に入れなければならないというルールがあります。
<Text>文字列が入れます</Text>
<View>文字列は入れません</View> //Invariant Violation: Text strings must be rendered within a Text component
私が怒られたエラーと同じですね。つまり、私は文字列をText
タグに入れなかったらエラーに怒られたことになります。
それはわかりましたが、でも文字列を画面表示してるわけでもないし、Image
のsource
属性で使ってるだけなのになんで? と思いました。もうちょっと深ぼる必要がありそうです。
2-2. React のレンダリング
Reactの公式サイトにはこういう説明があります。
false
,null
,undefined
, andtrue
are valid children. They simply don’t render. These JSX expressions will all render to the same thing:
<div />
<div></div>
<div>{false}</div>
<div>{null}</div>
<div>{undefined}</div>
<div>{true}</div>
true
、false
、null
、undefined
は有効(valid)な値ではあるけど React が無視するのでレンダリングしません! ということです。だから上記のサンプルコートは結果的に全部空のdiv
をレンダリングすることになります。
こういう特徴を利用して&&
演算子を用いた React コンポーネント制御ができます。Boolean
だけ入るshowHeader
という変数があるとしましょう。以下のコードはshowHeader
がtrue
のときだけHeader
コンポーネントをレンダリングします。showHeader
がfalse
のときは React が無視するはずですからね。
<div>
{showHeader && <Header />}
<Content />
</div>
しかし、Reactの公式サイトはこういう注意点も付けています。
One caveat is that some “falsy” values, such as the 0 number, are still rendered by React.
いくつかの falsy な値は無視されずにレンダリングされるということです。例では数字の0
をあげてますが、空の文字列(""
)も同じく無視されずにレンダリングされる対象です。
// errorMessage が "" の場合、errorMessage のほうがレンダリングされる
{errorMessage && <Error />}
// itemPrice が 0 の場合、itemPrice のほうがレンダリングされる
{itemPrice && <Item />}
ここまで理解したらわかってきました。もう一回私のコードを見てみましょう。
<View>
{imageUrl && <Image source={{ uri: imageUrl }} />}
</View>
imageUrl
はstring
型で、""
が入るときもあります。ですが、つい先話した通り文字列はたとえ空の文字列だとしても React は無視しません。だから&&
の左にあるimageUrl
は立派なText
タグの外に存在する文字列になってしまい、それに相応するエラーが発生したという流れです。
3. 解決
解決は結構簡単で、色々な方法があります。
3-1. 三項演算子
まずは&&
ではなくて三項演算子を使う方法です。これだったらImage
タグを返すかnull
を返すかの二択なので、ルール違反ではありません。
<View>
{imageUrl ? <Image source={{ uri: imageUrl }} /> : null}
</View>
3-2. Boolean 化
次はimageUrl
を Boolean 化する方法です。
<View>
{!!imageUrl && <Image source={{ uri: imageUrl }} />}
</View>
<View>
{Boolean(imageUrl) && <Image source={{ uri: imageUrl }} />}
</View>
!!
で強制的に Boolean 化することもできますが、個人的にはそんなに可読性が良い方法ではないと思いますので、好きではありません。Boolean()
のほうがわかりやすい気がします。
3-3. 採用案
私が採用した方法はこれです。
<View>
{imageUrl !== '' && <Image source={{ uri: imageUrl }} />}
</View>
imageUrl !== ''
もtrue
かfalse
を返すので、これも Boolean 化の一種ですね。三項演算子を使う方法より&&
を使ったほうが簡潔に書けるし、これだったら「imageUrl
が空文字列ではない場合だけImgae
タグを使う」という意図を明確に伝えられると思ってこの方法を採用しました。
終わり
ぐぐってみたら2018年に同じ Issueがあがってました(結構前の Issue だった)
Discussion