虎の穴ラボ技術ブログ

虎の穴ラボ株式会社所属のエンジニアが書く技術ブログです

MENU

個人的にReactをさわりはじめた時に知りたかったこと

こんにちは、虎の穴ラボFantiaエンジニアのNSYです。

Fantiaではフロントエンドに、JavaScriptのライブラリであるReactを一部採用しており、特に複雑な画面を作る際に積極的に使っています。 今回は初心者向けに、個人的にReactをさわりはじめた時に知っておきたかったなと感じたことを3つ紹介したいと思います。

1. カスタムフックによるロジックの分割

Reactには便利なフックが多く存在します。

それら便利なフックを使用することでパフォーマンスを意識した開発をすることができます。一方で要件が複雑になると、コードも複雑になり段々と苦しくなる場合があったりします。

カスタムフックはそれらの要件を、シンプルに実現することができる場合があります。カスタムフックとはフックを組み合わせて再利用可能な独自のフックを作成する機能になります。

ja.react.dev

例として増減とリセットができるシンプルなカウンターを作成します。

import { useState } from "react";

export const useCounter = (initialValue: number = 0) => {
  const [count, setCount] = useState(initialValue);

  const increment = (value: number = 1) => setCount(count + value);
  const decrement = (value: number = 1) => setCount(count - value);
  const reset = () => setCount(initialValue);

  return { count, increment, decrement, reset };
};

組み込みフックだけでも同じことはできますが、管理するstateや値を更新するロジックが複雑になった際にコンポーネントの行数が増え複雑になりがちな気がします。カスタムフックにロジックを切り出すことでコンポーネント自体はシンプルに保つことができます。

私も過去には、useReducerなどの組み込みのフックがすごく便利だと思いガンガン活用していました。しかし、開発が進みロジックが複雑になったタイミングでコンポーネントも複雑になり管理が難しくなりました。その際にカスタムフックの存在を知り、コンポーネントとロジックの分割ができること、私の使い方ではuseStateやuseEffectなどの組み合わせの方が管理しやすいことから、カスタムフックへ切り替えを行ったことがあります。

2. コンポーネント分割による効率的なレンダリング

ReactではUIを要素ごとにコンポーネントに分割することでコードの再利用性を高めることができます。

コンポーネントを分割することでコンポーネント間に親子関係が発生します。また、Reactのルールとしてpropsが変更されると再レンダリングが実行されます。

親コンポーネントが更新されると子コンポーネントも再レンダリングされます。一方で子コンポーネントが更新されたとしても、親のstateが更新されなければ親コンポーネントは再レンダリングされません。そのため適切なコンポーネント分割はレンダリング範囲の分割にもなる場合があります。

ja.react.dev

例として、同じ処理を分割する場合(A)としない場合(B)で見てみます。

function PatternA(props: { count: number; setCount: (count: number) => void }) {
  return (
    <>
      <div>
        <h2>パターンA</h2>
        <div>{props.count}</div>
        <button onClick={() => props.setCount(props.count + 1)}>+</button>
      </div>
    </>
  );
}

function PatternB() {
  const [count, setCount] = useState(0);

  return (
    <>
      <div>
        <h2>パターンB</h2>
        <div>{count}</div>
        <button onClick={() => setCount(count + 1)}>+</button>
      </div>
    </>
  );
}

export function Parent() {
  const [count, setCount] = useState(0);

  return (
    <>
      <PatternA {...{ count, setCount }} />
      <PatternB />
    </>
  );
}

実行

Aのボタンを押した場合は要素全体が再レンダリングされます。反対にBのボタンを押した場合は分割したコンポーネントは再レンダリングされますが、親コンポーネントはされません。

このことからも、コンポーネントの分割方法により再レンダリングされる範囲も分割されていることが分かります。レンダリング範囲を意識したコンポーネント設計ができると幸せになれる場面があるかもしれません。

3. コンポジションでの柔軟なUI作成

Reactではコンポジションにより、共通の構成を持つ柔軟なUIを簡単に作成できます。

コンポジションは、コンポーネントの中に他のコンポーネントを含めることで、同じレイアウトや動作を複数の場所で再利用することができます。
具体的には、属性値(props)としてReactNodeやReactElementといったデータ型を受け取り、それに基づいて表示内容を設定します。

ja.legacy.reactjs.org ja.react.dev

例としてタブを作成してみます。

// 表示するタブの中身
export type TabPanelProps = {
  title: string;
  children: ReactNode;
};

export function TabPanel(props: TabPanelProps) {
  return <div className="tab-panel">{props.children}</div>;
}
// タブ名と全体のレイアウト
type Props = {
  children: ReactElement<TabPanelProps>[];
};

export function TabList(props: Props) {
  const [activeId, setActiveId] = useState(0);
  const children = props.children.filter((child) => child != null);
  const titles = children.map((child) => child?.props.title ?? "");

  return (
    <div className="tabs">
      <ul>
        {titles.map((title, index) => (
          <li key={`tab-${index}`} onClick={() => setActiveId(index)}>
            {title}
          </li>
        ))}
      </ul>
      <div>{children[activeId]}</div>
    </div>
  );
}
// タブの利用方法
export function Tab() {
  return (
    <>
      <TabList>
        <TabPanel title="タブ1">
          <div>タブ1のコンテンツ</div>
        </TabPanel>
        <TabPanel title="タブ2">
          <div>タブ2のコンテンツ</div>
        </TabPanel>
        <TabPanel title="タブ3">
          <div>タブ3のコンテンツ</div>
        </TabPanel>
      </TabList>
    </>
  );
}

タブの切り替えを行う要素と、実際に情報を表示する領域で構成されたシンプルなタブのレイアウトを作成することができます。

私も過去にコンポジションの存在を知る前には、電話番号用のフォームやメールアドレス用のフォームなどレイアウトの一部が異なるUIをそれぞれ別に定義していました。そのためUIのベース部分に修正が入るとすべてのコンポーネントを修正する必要がありました。後にコンポジション採用によりレイアウトを共有することで修正が行いやすくなりました。

その他にはロジック中で変数にjsxに入れてから、returnのタイミングでコンポーネントに渡す書き方をしていました。この書き方が間違いというわけではありませんが、時間が経って見返した時にコンポーネントに渡されている値がjsxであるか分かりづらくなってしまっていました。そこでコンポジションを使用した書き方に変更することで分かりづらさを解消したこともありました。

まとめ

今回は個人的に見落としていて、もっとはやく知りたかったReactの機能について書かせていただきました。

Reactはシンプルな機能の組み合わせで複雑な機能をつくることができる点が魅力だと感じています。 FantiaでもReactを導入したことでシンプルにUIを実装できるようになったと感じています。本投稿が皆様の何かの役に立てば幸いです。

虎の穴ラボでの開発に興味がある方はぜひ下記採用情報をご覧ください。
カジュアル面談も実施しておりますので興味がある方はそちらもご利用いただければと思います。

Fantia開発採用情報

虎の穴ラボでは現在、一緒にFantiaを開発していく仲間を積極募集中です!
多くのユーザーに使っていただけるtoCサービスの開発をやってみたい方は、ぜひ弊社の採用情報をご覧ください。
toranoana-lab.co.jp