Reactの基礎 - 条件レンダリング

提供: MochiuWiki : SUSE, EC, PCB

概要

条件レンダリングとは、特定の条件に基づいてUIの表示内容を切り替えるReactの基本的な技術である。
Reactでは、TypeScript / JavaScriptの標準的な制御フロー構文をそのまま活用して、コンポーネントの描画内容を動的に変更できる。

下表に、条件レンダリングの主な手法を示す。

条件付きレンダリングの手法
手法 説明
if文によるレンダリングの分岐 完全に異なるJSXツリーを返す場合に使用する。
三項演算子(条件 ? A : B) シンプルな値の切り替えに使用する。
論理 AND 演算子(&&) 条件が真の場合にのみ要素を表示する。
変数への代入 複雑な条件や複数属性の変更に使用する。
オブジェクトマッピング 複数の選択肢から1つを選ぶ場合に使用する。


下表に、手法の選択ガイドを示す。

条件レンダリング手法の選択ガイド
手法 用途
if/else 完全に異なるJSXツリーを返す場合
三項演算子 シンプルな値の切り替え
&& 条件が真の時だけ表示する場合
変数代入 複雑な条件、複数属性の変更
オブジェクトマッピング 複数の選択肢から1つを選ぶ場合



if文によるレンダリングの分岐

コンポーネント内でのif文

if文を使用すると、コンポーネントが返すJSXツリーを条件によって完全に切り替えることができる。

以下の例では、isPacked プロパティの値に応じて異なるJSXを返している。

 interface ItemProps {
    name: string;
    isPacked: boolean;
 }
 
 function Item({ name, isPacked }: ItemProps) {
    if (isPacked) {
       return <li className="item">{name} </li>;
    }
    return <li className="item">{name}</li>;
 }


isPackedtrue の場合はチェックマーク付き、false の場合は通常のアイテムを表示する。
return文が2つあることで、条件ごとに完全に異なるJSXツリーを返すことができる。

early return パターン

ガード節とも呼ばれるearly returnパターンは、ローディング状態やエラー状態など、特殊な状態を先に処理してから通常のレンダリングを行う手法である。

以下の例では、ローディング中とエラー状態を先にチェックして早期に返却している。

 interface DataDisplayProps {
    isLoading: boolean;
    error: Error | null;
    data: { name: string };
 }
 
 function DataDisplay({ isLoading, error, data }: DataDisplayProps) {
    if (isLoading) {
       return <div>ローディング中...</div>;
    }
    if (error) {
       return <div>エラーが発生しました: {error.message}</div>;
    }
    return <div>{data.name}</div>;
 }


このパターンには以下に示すメリットがある。

  • 特殊なケースを先に処理することで、通常フローのインデントが深くなることを防ぐ。
  • ローディング中やエラー状態を明示的に処理することで、処理の意図が読み取りやすくなる。
  • 複数の条件を順次チェックする時に可読性が高い。



JSX内での条件レンダリング

三項演算子 (条件 ? A : B)

三項演算子は、JSXの中で直接条件分岐を記述できる方法である。
条件 ? 真の場合の値 : 偽の場合の値 という形式で記述する。

以下の例では、isPacked の値によって表示するJSXを切り替えている。

 // ItemProps型は上記で定義済み
 return (
    <li className="item">
       {isPacked ? (
          <del>{name + ' ✅'}</del>
       ) : (
          name
       )}
    </li>
 );


isPackedtrue の場合は取り消し線付きのテキスト、false の場合は通常のテキストを表示する。

三項演算子を使用する時は、以下に示す点に注意する。

  • ネストが深くなると可読性が低下する。
  • 複雑な条件の場合は、変数への代入や子コンポーネントへの抽出を検討する。
  • シンプルな値の切り替えに最も適している。


論理AND演算子 (&&)

論理AND演算子 (&&) は、条件が真の場合にのみ要素を表示したい場合に使用する。
条件が偽の場合は何も表示しない。

以下の例では、isPackedtrue の場合のみチェックマークを表示している。

 return (
    <li className="item">
       {name} {isPacked && '✅'}
    </li>
 );


isPacked がfalseの場合、false && '✅' はfalseとなり、Reactは何もレンダリングしない。

&&演算子の注意点

&& 演算子を使用する時は、falsyな値の扱いに注意が必要である。
JavaScriptでは、0NaN 等の数値もfalsyな値であるが、Reactはこれらをそのまま画面に表示する場合がある。

下表に、各falsyな値のレンダリング挙動を示す。

falsyな値のレンダリング挙動
レンダリング結果
0 number 0が画面に表示される。 (注意が必要)
"" string 何も表示されない。
null object 何も表示されない。
undefined undefined 何も表示されない。
false boolean 何も表示されない。
NaN number NaNが画面に表示される。(注意が必要)


0 や NaNは画面に表示されてしまうため、数値を条件として使用する場合は明示的な比較演算子を使用することを推奨する。

問題のあるコードと正しいコードの例を以下に示す。

 // 正しいコード : 明示的な比較演算子を使用する
 messageCount > 0 && <p>新しいメッセージがあります</p>
 
 // 問題のあるコード : messageCountが0の場合、0が表示される
 const messageCount: number = 0;
 messageCount && <p>新しいメッセージがあります</p>



変数への代入パターン

JSXを変数に代入する

JSXを変数に代入するパターンは、複数の属性を条件によって変更する場合や条件分岐が複雑になる場合に最も柔軟な方法である。

以下の例では、itemContent 変数にJSXまたはテキストを代入してから、最終的なJSXに埋め込んでいる。

 // ItemProps型は上記で定義済み
 function Item({ name, isPacked }: ItemProps) {
    let itemContent = name;
    if (isPacked) {
       itemContent = <del>{name + ' ✅'}</del>;
    }
    return <li className="item">{itemContent}</li>;
 }


このパターンには以下に示すメリットがある。

  • 条件ロジックとJSXの構造を分離できるため、ソースコードが読みやすくなる。
  • 複数の変数を条件によって変更する場合に対応しやすい。
  • if / else以外にも、switch文や複雑なロジックを組み合わせやすい。



null / undefinedを返す場合

コンポーネントから null または undefined を返すと、そのコンポーネントは何もレンダリングしない。
Reactはこれを ツリーのホール として扱い、対応するDOM要素は一切作成されない。

以下の例では、isPacked がtrueの場合にnullを返すことにより、アイテムを非表示にしている。

 // ItemProps型は上記で定義済み
 function Item({ name, isPacked }: ItemProps) {
    if (isPacked) {
       return null;
    }
    return <li className="item">{name}</li>;
 }


null を返す場合の注意点を以下に示す。

  • コンポーネント自体は存在するが、UIには何も表示されない。
  • 親コンポーネントからは、このコンポーネントを条件付きで含めないようにする方が一般的である。
  • null を返すパターンは、コンポーネントが必ず何かを返す必要がある場合に有用である。



複数条件の処理

オブジェクトマッピングパターン

複数の選択肢から1つを選ぶ場合は、オブジェクトをマッピングテーブルとして使用することにより、if / else文や三項演算子のネストを避けることができる。

以下の例では、飲み物の名前をキーとしてオブジェクトから情報を取得している。

 interface DrinkInfo {
    part: string;
    caffeine: string;
 }
 
 const drinks: Record<string, DrinkInfo> = {
    tea: { part: 'leaf', caffeine: '15-70 mg/cup' },
    coffee: { part: 'bean', caffeine: '80-185 mg/cup' }
 };
 
 interface DrinkProps {
    name: keyof typeof drinks;
 }
 
 function Drink({ name }: DrinkProps) {
    const info = drinks[name];
    return (
       <section>
          <h1>{name}</h1>
          <p>Part: {info.part}</p>
          <p>Caffeine: {info.caffeine}</p>
       </section>
    );
 }


オブジェクトマッピングパターンには以下に示すメリットがある。

  • 選択肢が増えても、オブジェクトにエントリを追加するだけで対応できる。
  • コンポーネント本体のロジックがシンプルに保たれる。
  • データの追加・変更が容易で、保守性が高い。


このパターンは、タブの切り替えや状態に応じた表示内容の変更等、複数の選択肢がある場面で特に効果的である。


関連情報