【React】JSXのHTML部分に長い処理書くのやめないか?

本投稿は TECOTEC Advent Calendar 2023 の18日目の記事です。

はじめに

こんにちは。次世代デジタル基盤開発事業部の熊谷です。
普段はWebアプリケーションのフロントエンド・バックエンドの開発に携わっています。

最近はコードレビューやプロジェクトへの途中参画などで他人のコードを読むことも増えてきたのですが、個人的にこういう書き方はやめてくれ~と思うことがある度にSlackでつぶやいていた所、Tips的な感じで記事にしてほしいと反応がありました。
なのでちょくちょく気になったことについて、ぼやき記事としてまとめていきたいと思います。

今回は、Reactのコンポーネントを書くときに使われるJSXのHTML部分の書き方についてです。
(もしかしたらVueの似たような部分についても言えるかも…?)
自分なりにコードを読みやすくするためのポイントをまとめていくので、何かの参考になれば幸いです。

JSXのHTMLっぽい所

Reactでアプリケーションを作成する時にはほぼ必ずJSXを使って書くと思います。
JavaScriptのファイルの中にHTMLっぽく書けるところがあり、最初は戸惑うかもしれません。

(あのreturn部分のHTMLっぽい所って正式な名称あるんですかね…?調べても分からなかったので、ここではとりあえずHTML部分と呼ぶこととします。)

けれども、あのHTML部分は意外と書き方に柔軟性があり、慣れると思うようにページやコンポーネントを作成できて結構便利だったりします。

ja.react.dev

ただ、あのHTML部分はJavaScriptも使って書いていくので、HTMLの記述とJavaScriptの記述が混在する状態になり、何も考えずコードを書いていくといつの間にか読みにくいコードが出来上がっていたりします。

コード量の少ない小さいコンポーネントであれば問題になる事は少ないですが、何百行もある巨大なコンポーネントがそうなっていると怪しくなってきます。
HTMLとJavaScriptがあまりに混在しすぎると、確認したい部分が表示される条件を追いづらくなり、結果保守するのも憚られるコードになってしまいます。

なのでHTML部分を書く場合は、コードが読みづらいものになっていないかを意識して書いて欲しいものです。

JSXのHTML部分に長い処理を書かないようにしよう!

では、どのようにすれば読みやすく、保守しやすいコードにできるでしょうか?

私は表題に書いてある通り、「HTML部分にはなるべく長いJavaScriptによる処理を書かないようにすること」が大事だと思います。
HTML部分は「データを表示する」という役割を徹底し、表示条件の計算や表示用データの生成はreturn文の前にJavaScript側で処理するように意識して書くと、比較的読みやすくなります。

1行で済むような処理については特に問題になりませんが、複数行に渡る処理が沢山書かれていると可読性が落ちます。
そのような場合は、使いたい値を事前に計算しておき、分かりやすい名前の変数に保存して(ここ重要)、その変数を指定するだけの方が可読性が上がる場合が多いと思います。
(可能であればuseMemoなどでメモ化しておくと、パフォーマンス面でもさらに良し。)

具体的に意識すると良い部分について、何点か掘り下げます。

HTMLタグやコンポーネントの属性の値の計算

コンポーネントのPropsとして様々な値をHTMLタグやコンポーネントに指定すると思いますが、その値の計算をタグ内で行うと読みづらくなります。
例えば次のような感じ。

function NotGoodComponent() {
  // ~~~ いろいろな処理 ~~~
  return (
    <button
      className={
        isMobile ? (isAndroid ? 'android-class' : 'iphone-class') : 'pc-class'
      }
      onClick={() => {
        setIsButtonClick(true)
        setIsOpen(true)
        fetchData()
          .then((data) => {
            setData(data)
          })
          .catch((err) => {
            console.error(err)
          })
      }}
      disabled={isButtonClick || isOpen || someDisableFlag}
    >
      サンプルボタン
    </button>
  )
}

これぐらいの量であれば、一画面で見えるのでまあ大丈夫そうですが、各処理の量が増えたり似たようなコードが沢山あるとHTML部分が読みづらく感じます。
なので、それぞれの属性に渡す値をreturn文の前であらかじめ計算・定義しておき、それを指定するようにした方がスッキリして良いです。

function BetterComponent() {
  // ~~~ いろいろな処理 ~~~
  return (
    <button
      className={buttonClass}
      onClick={onButtonClick}
      disabled={buttonDisabled}
    >
      サンプルボタン
    </button>
  )
}

要素の表示条件の計算

特定の要素を条件付きで表示したい場合、論理積演算子 && を使って、条件式が true の場合のみ要素を表示するというやり方をすることも多いですよね。
その条件の計算についても、式が簡単だと直接書いてしまいがちですが、これもなるべくHTML部分に記載しない方が良いです。
例えば次のような場合です。

function NotGoodComponent() {
  // ~~~ いろいろな処理 ~~~
  return (
    <>
      {displayStartDate <= nowDate && (
        <div>
          <p>お知らせ</p>
        </div>
      )}
    </>
  )
}

ここではお知らせが表示される期間をチェックし、表示日が来たら要素を表示するという処理を行っています。
これも単体で見ればすぐ分かる条件ですが、このような条件がHTML部分に沢山あったり、条件式がさらに複雑だったりすると、いちいち頭で把握しながら読み進めるのが大変になります。
そのため、あらかじめ表示条件を計算してフラグ変数として保存しておき、計算済みの変数を元に表示を切り替えるようにした方が理解しやすくなります。

function BetterComponent() {
  // ~~~ いろいろな処理 ~~~
  return (
    <>
      {afterDisplayDate && (
        <div>
          <p>お知らせ</p>
        </div>
      )}
    </>
  )
}

上記のように、表示日を過ぎているかを示すフラグ変数に計算結果を入れるようにし、それを表示条件として使う事で、このHTML部分をパッと見ただけでどういう場合に表示されるかが分かりやすくなります。
(例では英語的に変な変数名になっていそうですが、見ただけで条件が分かるような変数名になっていればOKです。)

三項演算子

要素の表示条件の記載と似ていますが、表示する要素を切り替える手法として三項演算子もよく使われます。
三項演算子は便利な反面、式が長かったり三項演算子がネストされていたりすると読みづらくなりやすいです。

function NotGoodComponent() {
  // ~~~ いろいろな処理 ~~~
  return (
    <>
      {isLogin ? (
        <div>
          {/* ログイン中用の長い表示内容 */}
          <p>ログイン中</p>
        </div>
      ) : (
        <div>
          {/* ログインしていない時の長い表示内容 */}
          <p>ログインしていません</p>
        </div>
      )}
    </>
  )
}

例のごとく、これくらいのコード量であれば問題ありませんが、 true の時の表示内容や false の時の表示内容が多かったり、三項演算子がネストされていたりすると読みづらくなります。
(私の場合、両方のパターンが混ざったコードを目にしたことがあり、頭を抱えたことがあります。)

なので、表示内容が長くなる場合は三項演算子を使わず、あえて二つの式に分けて書くのも一つの手です。

function BetterComponent() {
  // ~~~ いろいろな処理 ~~~
  return (
    <>
      {isLogin && (
        <div>
          {/* ログイン中用の長い表示内容 */}
          <p>ログイン中</p>
        </div>
      )}

      {!isLogin && (
        <div>
          {/* ログインしていない時の長い表示内容 */}
          <p>ログインしていません</p>
        </div>
      )}
    </>
  )
}

このように二つの式に分けることによって、その部分の表示条件を見つけやすくなります。

そもそもの話ですが、表示内容が大きい事が可読性の低下につながっているのであれば、その部分を別コンポーネントに切り出してしまうというのも改善案の一つです。
コンポーネントを分けてしまえば、三項演算子での表示切替も違和感が無くなるかもしれないので、場合によって使い分けてみましょう。

mapによる要素の生成

配列に格納されているデータの数だけ要素を生成する場合、よく配列の組み込みメソッドの map が使われます。
map は便利なのでよく使いますが、 map の中の要素生成処理が長くなると可読性が落ちる要因の一つになります。

function NotGoodComponent() {
  // ~~~ いろいろな処理 ~~~
  return (
    <>
      {messageList.map((message) => {
        const buttonLabel = message.isRead ? '既読' : '未読'
        const onButtonClick = () => {
          setReadStatus(message.id)
        }
        return (
          <div key={message.id}>
            <p>{message.message}</p>
            <button onClick={onButtonClick}>{buttonLabel}</button>
          </div>
        )
      })}
    </>
  )
}

上記の例だと、そもそも map の処理の中にあまり処理を書かなくても見やすく書けるのでいい例ではないかもしれませんが、このような感じでHTML部分でmapのコールバック関数を定義すると、そのコールバック関数の処理が長くなったときに読みづらくなります。

そのような場合には、コールバック関数をHTML部分の外側で定義して外出しするとスッキリします。
もしくは、繰り返し生成したい部分をまとめて別コンポーネントとして定義し、それを呼び出すようにすると読みやすくなります。

function BetterComponent() {
  // ~~~ いろいろな処理 ~~~
  // 外部にメッセージ表示用のMessageコンポーネントを実装しているとする
  return (
    <>
      {messageList.map((message) => (
        <Message key={message.id} message={message} />
      ))}
    </>
  )
}

とはいえ、私の経験上ですが、mapでの要素生成はコールバック関数をHTML部分に記載しても、あまり可読性が落ちにくい気がします(慣れの問題?)。
なので、無理に処理を切り出すよりも、数行の処理であればそのまま記載していた方が分かりやすいかもしれません。

その辺りは、プロジェクトの方針だったり、実際のコードを読んで読みづらいと思った場合などに、臨機応変に対応していただければと思います。

その他の対策

ちょっと本題とは逸れますが、その他にコードを読みやすくするためにやって欲しい事があります。

HTML部分の記載についてというよりも、プログラミング全般に言えることですが、やはり適度にコメントは入れて欲しいです。

コード量が1画面に収まるくらいであれば無くても大丈夫ですが、HTML部分が長くなってしまうと、そのコンポーネントがどんな構成になっているのかが分かりづらくなります。
そこが何の表示部分なのかを少しコメントを入れるだけで、コードを読む側としてはだいぶ助かります。
(最終的に、アプリケーションをビルドする時にはコメント行を削除するようにしておくとスマートですね!)

また、コメントを書くのがどうしても嫌だという場合には、要素のまとまりごとに空行を入れるだけでも読みやすくなります。
いくつかのdiv要素が空行も開けずに羅列してあると、パッと見たときにどこまでが関連のある要素なのかが分かりづらいので、適度に空行を入れることをお勧めします。

おわりに

Reactで使われるJSXのHTML部分の書き方について、可読性や保守性を向上させるためのテクニックを紹介しました。
まとめると、HTML部分で書いている処理を別に定義してそれを呼び出すようにし、表示のためのロジックを書く事に終始するようにしよう!という事です。

しかしこの方法はすべてのケースに当てはめられるものではありません。

こんな事に頭のリソースを割くまでもなく、分かりやすいコンポーネント設計になっている場合にはもちろん不要です。

細かい単位でコンポーネントが分けてあれば、HTML部分に処理が書いてあった方が一度に処理を確認できるので、私の紹介した方法だと逆に可読性が落ちる場合もあるはずです。
なので、これらのテクニックは臨機応変に、「なんかこのコード、読みづらいな」と感じたときに使ってもらえればと思います。

最後に、JSXについて記事の上部でReactのドキュメントのリンクを貼りましたが、サバイバルTypeScriptのJSXの記事も参考になるのでリンクを載せておきます。

typescriptbook.jp

ここまで読んでいただきありがとうございました。

テコテックの採用活動について

テコテックでは新卒採用、中途採用共に積極的に募集をしています。
採用サイトにて会社の雰囲気や福利厚生、募集内容をご確認いただけます。
ご興味を持っていただけましたら是非ご覧ください。

tecotec.co.jp