ロカオプ技術ブログ

株式会社ロカオプの技術ブログです。

React でやってはいけないBad Practiceを12個まとめました (TypeScript版)

概要

みなさんこんにちは。フルスタックエンジニアの高瀬 @takasehiromichi です。

Reactは、記述の自由度が高い一方で、Bad Practiceとされるものがあり、「よくない」書き方が存在します。

エンジニアによって、Bad Practiceを把握している人と、そうでない人がいるため、Bad Practiceについて記事にしようと思います。

01. 過剰なProp Drilling

Reactはpropsを子componentに渡すことができますが、技術的には無限にpropsを渡し続けることができます。 (親 > 親の子 > 親の子の子 > 親の子の子の子 ...)

しかし、過剰なprop drillingは、様々な苦痛を伴います。

stateが更新された時、propsが更新された時、親componentがre-renderされた時に、componentはre-renderされます。

そのため、過剰なprop drillingはre-renderの問題を引き起こす可能性があります。

また、過剰なprop drillingは可読性を低下させます。

そのため、propsを渡す数はなるべく少なくなるよう努力した方がいいでしょう。

個人的には、useContextの使用をおすすめします。また、componentは、親、子、孫までとし、それ以上になりそうな時は、コードの構成を見直します。

02. 過剰なimport

例えば、Material UIのButtonを使用したい場合に、

import * as MUI from '@material-ui/core';

<MUI.Button>aaa</MUI.Button>

と、書くことができます。

しかし、この記述はcoreの全てをロードするため、非常に冗長です。

そのため、本当に必要なものだけロードできるように記述するのが望ましいです。

import { Button } from '@material-ui/core';

<Button>aaa</Button>

03. component内に過剰にビジネスロジックを記述する

component内にビジネスロジックが大量に記述されていると、可読性が低下します。

componentとしての粒度が適切か、useReducer、useContextで代替可能か、ビジネスロジックをutilとして外出しできないかを検討すべきです。

component内のビジネスロジックは必要最低限とし、あくまでUIを描画することをメインとした方がいいでしょう。

04. 過剰なre-render

componentとして切り分けていると、過剰なre-renderが発生することがよくあります。

useMemo、useCallbackを適切に使用して、過剰なre-renderを抑制しましょう。

05. ナンニデモuseEffect

componentがrenderされたときに処理したいからと言って、ナンニデモuseEffectを使用すればいいということではありません。

Reactが推奨するuseEffectの使用方法は以下リンク先にありますので、参考にした方がいいでしょう。

beta.reactjs.org

いくつか抜粋します。

  • componentが表示されている間、ネットワーク、API、ライブラリと接続したままにしておき、その後、接続を解除する必要がある
  • setInterval()とclearInterval()
  • windows.addEventListener()とwindow.removeEventListener()
  • animation.start()とanimation.reset()
  • カスタムフックで利用
  • 非 React ウィジェットの制御
  • fetch

06. ナンニデモ &&

&& は便利ですが、反面、意図しない挙動になる可能性があります。

例えば、以下のコードは0を表示します。

const amount = 0;
return <span>{amount && `現在の数量は ${amount} です。`}</span>;

また、UI描画部分にビジネスロジックが混入しているため、好ましくありません。

以下のように、明示的にnullを返した方がいいでしょう。

const Component = ({ amount }: { amount: number }): JSX.Element | null => {
  if (amount) {
    return <span>{`現在の数量は ${amount} です。`}</span>;
  } else return null;
};

const amount = 0;
return <Component amount={amount} />;

07. ナンニデモ三項演算子

三項演算子は便利ですが、使い所を誤ると、可読性の低下を招きます。

例えば、以下はUI描画部分に三項演算子を使用した例です。

これはまだ読めるかもしれませんが、この記述の仕方が複数個、至る所で使用されていたらどうでしょうか。きっと読みにくいと思います。

ビジネスロジックは、関数かcomponentに切り出しましょう。

  const Component = ({ isAdmin }: { isAdmin: boolean }): JSX.Element => {
    return (
      <>
        <span>これが共通的な文章だとして</span>
        <div>
          {isAdmin ? (
            <>
              <div>これはとてもadminなテキストですね</div>
              <div>
                <span>例えばこんな文が来て</span>
                <div>
                  <span>こんな文が来たりとかして</span>
                </div>
              </div>
            </>
          ) : (
            <>
              <div>これはadminではないですね</div>
              <span>例えばこんな文が来て</span>
              <div>
                <span>こんな文が来たりとかして</span>
                <span>こんな文も来たりとかして</span>
              </div>
            </>
          )}
        </div>
        <span>ここにfooterなんかが来たりして</span>
      </>
    );
  };

  const isAdmin = true;
  return <Component isAdmin={isAdmin} />;

以下は、関数にまとめてみた例です。

  const renderAdminText = (isAdmin: boolean): JSX.Element | undefined => {
    let adminTextJSX = null;

    if (isAdmin) {
      adminTextJSX = (
        <>
          <div>これはとてもadminなテキストですね</div>
          <div>
            <span>例えばこんな文が来て</span>
            <div>
              <span>こんな文が来たりとかして</span>
            </div>
          </div>
        </>
      );
    } else {
      adminTextJSX = (
        <>
          <div>これはadminではないですね</div>
          <span>例えばこんな文が来て</span>
          <div>
            <span>こんな文が来たりとかして</span>
            <span>こんな文も来たりとかして</span>
          </div>
        </>
      );
    }

    return <div>{adminTextJSX}</div>;
  };

  const Component = ({ isAdmin }: { isAdmin: boolean }): JSX.Element => {
    return (
      <>
        <span>これが共通的な文章だとして</span>
        {renderAdminText(isAdmin)}
        <span>ここにfooterなんかが来たりして</span>
      </>
    );
  };

  const isAdmin = true;
  return <Component isAdmin={isAdmin} />;

08. propsをそのまま使用する

propsをpropsたるまま使用すると、propsがどんなプロパティを持つかわからないため、可読性の低下や、あらぬバグを孕む危険性があります。

  const Component = (props: any) => {
    return (
      <div>
        <h1>{props.title}</h1>
        <span>{props.text}</span>
      </div>
    );
  };

  return <Component title="タイトル" text="テキスト" />;

XPropsTypeの型を定義の上、分割代入引数としてプロパティを受け取りましょう。

  type ComponentPropsType = {
    title: string;
    text: string;
  };
  const Component = ({ title, text }: ComponentPropsType) => {
    return (
      <div>
        <h1>{title}</h1>
        <span>{text}</span>
      </div>
    );
  };

  return <Component title="タイトル" text="テキスト" />;

09. React.VC、React.VFCを使用する

TypeScriptの文脈においては、React.VC、React.VFCを使用するメリットはありませんので、JSX.Elementを使用します。

※後述の参考記事を参照してください。

React.VCの使用

  type ComponentPropsType = {
    title: string;
    text: string;
  };
  const Component2: React.FC<ComponentPropsType> = ({
    title,
    text,
    children,
  }) => {
    return (
      <div>
        <span>{title}</span>
        <span>{text}</span>
        {children}
      </div>
    );
  };

React.VFCの使用

  type ComponentPropsTypeWithChildren = {
    title: string;
    text: string;
    children: JSX.Element;
  };
  const Component3: React.VFC<ComponentPropsTypeWithChildren> = ({
    title,
    text,
    children,
  }) => {
    return (
      <div>
        <span>{title}</span>
        <span>{text}</span>
        {children}
      </div>
    );
  };

JSX.Elementの使用

  type ComponentPropsTypeWithChildren = {
    title: string;
    text: string;
    children: JSX.Element;
  };
  const Component1 = ({
    title,
    text,
    children,
  }: ComponentPropsTypeWithChildren): JSX.Element => {
    return (
      <div>
        <span>{title}</span>
        <span>{text}</span>
        {children}
      </div>
    );
  };

10. 無名関数を使用したonXの汚染

onX内で無名関数を使用するとUIだけでなくビジネスロジックが混入し、可読性が低下します。

    const Component = () => {
      type eType = React.MouseEvent<HTMLButtonElement, MouseEvent>;

      const doSomething1 = (e: eType) => {
        console.log('doSomething1', e);
      };
      const doSomething2 = (e: eType) => {
        console.log('doSomething2', e);
      };

      return (
        <button
          onClick={(e) => {
            doSomething1(e);
            doSomething2(e);
          }}
        >
          test
        </button>
      );
    };

onXにはhandleXをあて、その中でビジネスロジックを記述します。

    const Component = () => {
      type eType = React.MouseEvent<HTMLButtonElement, MouseEvent>;

      const doSomething1 = (e: eType) => {
        console.log('doSomething1', e);
      };
      const doSomething2 = (e: eType) => {
        console.log('doSomething2', e);
      };

      const handleOnClick = (e: eType) => {
        doSomething1(e);
        doSomething2(e);
      };

      return <button onClick={handleOnClick}>test</button>;
    };

11. インラインスタイルを使用する

インラインスタイルは大きく可読性を低下させます。

基本的には、emotionを使用した方がいいですが、組織文化に合わせて選定するのがいいでしょう。

emotionなら、JSX Pragmaも不要で、以下だけで動きます。

tsconfig.json

{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "@emotion/react"
  }
}

※後述の参考記事を参照してください。

12. import React from 'react'; を使用する

React 17からは、import React from 'react'; は不要です。

※後述の参考記事を参照してください。

まとめ

まだまだbad practiceはありそうですが、一旦ここまででまとめとしたいと思います。

参考記事

cult.honeypot.io

kray.jp

rahuulmiishra.medium.com

blog.logrocket.com

qiita.com

legacy.reactjs.org

dev.to