📘

普段から意識したいフロントエンドの実装方法

2024/02/19に公開

この記事では普段の実装で使えるような実践的な知識を紹介します。以降の実装方法紹介では、理解しやすいように「良い実装」「悪い実装」という表現をしていますが、時と場合によっては良し悪しが逆転することもあります。その上で、現在(2024/03/23)において、一般的にはこの方法で実装すると質が高まる可能性があるよ、という認識で読んでいただければと思います。

button タグには type を必ず書く

button タグの type には button, submit, reset の3種類の値があります。タグ名からデフォルトは type="button" だと思っている人も多いと思いますが、デフォルトは type="submit" です。

これによって、良く発生するのがフォームを実装した時に意図せずフォームを submit してしまうことです。例えば、フォームに入力値をクリアするためのボタンを設置した場合に、そのボタンに type が書かれていないと type="submit" として扱われてしまいます。このように意図しない挙動を防ぐためにも、type は必ず書いておくようにしましょう。

❌ 悪い実装

<button>button</button>

✅ 良い実装

<button type="button">button</button>

コンポーネント条件付きレンダリングには三項演算子を使う

論理演算子を使って条件付きレンダリングを行った場合、左辺に数値を指定してしまうとその数値がレンダーされてしまうという罠が存在します。意図せず数値が表示されてしまうのを防ぐためにも三項演算子を使っておきましょう。

これは React の公式サイトでも記載されています。

JavaScript は条件をテストする際、左の辺を自動的に真偽値に変換します。しかし、左の辺が 0 の場合は、式全体がその 0 という値に評価されてしまうため、React は何もレンダーしないのではなく 0 を表示します。

❌ 悪い実装

論理演算子

const Component = () => {
  return (
    <div>
      {condition && <ChildComponent />}
    </div>
  );
};

✅ 良い実装

三項演算子

const Component = () => {
  return (
    <div>
      {condition ? <ChildComponent /> : null}
    </div>
  );
};

アイテム同士の間隔は親要素(コンテナ要素)で設定する

「どのように配置されるのか」という情報はアイテムを配置する要素が知るべき情報です。これはコンポーネントに margin を持たせないようにするのと同じです。コンポーネント自身が余白を持ってしまうとそのコンポーネントを利用する時に予期せぬレイアウト崩れを起こす可能性があります。余白はコンポーネントを利用する側で設定することで柔軟な配置が可能となります。

❌ 悪い実装

<div>
  <div class="item">item1</div>
  <div class="item">item2</div>
  <div>item3</div>
</div>
.item {
  margin-bottom: 8px;
}

✅ 良い実装

フクロウセレクタ( * + * )

<div class="container">
  <div>item1</div>
  <div>item2</div>
  <div>item3</div>
</div>
.container > * + * {
  margin-top: 8px;
}

flex gap

<div class="container">
  <div>item1</div>
  <div>item2</div>
  <div>item3</div>
</div>
.container {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

ボーダーを消すには none ではなく 0 を設定する

クラスを複数指定した時に後から適用されるクラスで border-style: solid; を指定しているとボーダーが表示されてしまいます。確実にボーダーを非表示にするために 0 を設定します。このような実装が発生することは少ないと思いますが、事故防止のためにも 0 を設定しておくと安全です。

❌ 悪い実装

<div class="classA classB">item</div>
.classA {
  border: none;
}

.classB {
  border-style: solid;
}

✅ 良い実装

<div class="classA classB">item</div>
.classA {
  border: 0;
}

.classB {
  border-style: solid;
}

React Fragment をコンポーネントのルート要素に使わない

Fragment 自体が悪いわけではありません。コンポーネントのルート要素に使うことが良くないです。この使い方をすると意図せずデザインが崩れることがあります。例えば、下記の例のようなコンポーネントがあるとして、ChildComponent を縦に2行で並べようとした時は ParentComponent でルート要素で flex などのスタイルを指定すると思います。しかし、悪い実装の方の場合、ChildComponent のルート要素は DOM に出力されないので span 要素が縦に6行並んでしまいます。

このように、コンポーネントを1つの要素と考えるのが普通なので、ルート要素に Fragment を使ってしまうと1つのコンポーネントが複数の要素を返しているような状態になり、親コンポーネントのスタイル設定によっては意図せずデザイン崩れを発生させる要因になります。

❌ 悪い実装

const ParentComponent = () => {
  return (
    <div className={styles.hoge}>
      <ChildrenComponent />
      <ChildrenComponent />
    </div>
  );
};

const ChildComponent = () => {
  return (
    <>
      <span>item1</span>
      <span>item2</span>
      <span>item3</span>
    </>
  );
};

✅ 良い実装

const ParentComponent = () => {
  return (
    <div className={styles.hoge}>
      <ChildrenComponent />
      <ChildrenComponent />
    </div>
  );
};

const ChildComponent = () => {
  return (
    <div>
      <span>item1</span>
      <span>item2</span>
      <span>item3</span>
    </div>
  );
};

早期リターンを使う

早期リターンを使うことで関数の動作がわかりやすくなります。

❌ 悪い実装

1つの if 文がネストしており、複数の条件を確認しながらどの return が行われるかを読み解く必要があります。

const Hoge = (data) => {
  if (data !== undefined) {
    if (typeof data === 'string') {
      return 'data は文字列です!';
    } else if (typeof data === 'number') {
      return 'data は数値です!';
    } else {
      return 'data は文字列でも数値でもありません!';
    }
  } else {
    return 'data が存在しません!';
  }
}

✅ 良い実装

条件をできるだけ分割し、特定の条件ごとに return を行うことで、上から順番に読み解けます。

const Hoge = (data) => {
  if (data === undefined) {
    return 'data が存在しません!';
  }

  if (typeof data === 'string') {
    return 'data は文字列です!';
  }

  if (typeof data === 'number') {
    return 'data は数値です!';
  }

  return 'data は文字列でも数値でもありません!';
}

Discussion