【React/Next.js】「propsはコンポーネントの設計書」とは?propsの渡し方

2024/02/03に公開3

今日は現場の相互レビューで指摘された「propsはコンポーネントの設計書」という言葉について調べて、propsの渡し方について考えていこうと思う。

実際に指摘を受けたコード

index.tsx

type Pict = {
export type PictCardProps = {
  pictNo?: string
  filename?: string
}

const picts: Pict[] = [
 {
  pictNo: 1,
  filename: "test1.png",
 },
 {
  pictNo: 2,
  filename: "test2.png",
 },
 {
  pictNo: 3,
  filename: "test3.png",
 },

{picts.map((pict) => (
  <PictCard
    pictNo={pict.pictNo}
    filename={pict.filename}
  />
))}

上記のコードはPictCardコンポーネントにpropsを渡している。
渡すpropsの型は string|unedefined なので
これを受け取ろうとすると

PictCard.tsx

type PictCardProps = {
  pictNo?: string
  filename?: string
}

export const PictCard = ({
  pictNo,
  filename,
}: PictCardProps) => {

  return (
      <div>
        <div>
          <div>
            <Image
              id={pictNo ?? ""}
              src={filename ?? ""}
              alt={pictNo ?? ""}
            />
          </div>
          <div>
            <h3>{filename }</h3>
            <p>pictcardです</p>
          </div>
        </div>
      </div>

上記のようにstring|unedefinedをそのまま受け取り、受け取り側で使う場合
毎回 hogehoge ?? "" をつけることになる。

propsは渡す側で整えておく

今回指摘された内容は要するにpropsは渡す側で整えておくということだろう。
確かに受け取り側で使うたびにundefinedの場合を演算子で書くのはかなり不便な気もする。
受け取り先でもオプショナルで処理が変わる場合は別だけど。

では具体的にさっきのコードをどう書けばいいかっていうと

index.tsx

type Pict = {
export type PictCardProps = {
  pictNo?: string
  filename?: string
}

const picts: Pict[] = [
 {
  pictNo: 1,
  filename: "test1.png",
 },
 {
  pictNo: 2,
  filename: "test2.png",
 },
 {
  pictNo: 3,
  filename: "test3.png",
 },

{picts.map((pict) => (
  <PictCard
    // ここですでにundefinedの場合の渡す内容を書いておく
    pictNo={pict.pictNo ?? ""}
    filename={pict.filename ?? ""}
  />
))}

PictCard.tsx

type PictCardProps = {
 // undefinedの場合の内容は定義してあるのでオプショナルを消す
  pictNo: string
  filename: string
}

export const PictCard = ({
  pictNo,
  filename,
}: PictCardProps) => {

  return (
      <div>
        <div>
          <div>
            <Image
              // そのまま使える
              pictNo={pictNo}
              src={filename}
              alt={pictNo}
            />
          </div>
          <div>
            <h3>{filename }</h3>
            <p>pictcardです</p>
          </div>
        </div>
      </div>

こう書くことでコードの可読性も上がる。
コンポーネントの受け取りPropsの型を見ることで、
このコンポーネントでは文字列だけで使われているのか、undefinedの場合に他の処理を返すようにしているのか等一目瞭然ということから
「propsはコンポーネントの設計書」という記事があったのだろう。

あとがき

フロントエンドエンジニアとして現場に配属されて3ヶ月。
こういう指摘がある時に、個人開発が全然役に立たないことを実感する。
チーム開発である以上、コードの可読性はとても重要だし可読性を考えて実装するのは未経験ではなかなか出来ないことなので
未経験は門前払いされるのかな、と思ったり。
SESあるあるの「経験3年」という肩書きを背負っているので、こういう時にボロが出るし
きっと「3年でこれってなかなかポンコツ」と思われているに違いない。笑
それでも未経験の自分を拾ってくれた自社には感謝しかないけれど。

この記事が新人さんや未経験の目に止まって、初心者脱却の1つになれば幸いです😭

Discussion

Honey32Honey32

失礼します、差し出がましいアドバイスで恐縮ですが、

仕様によってどのような Props にするのが最善かは変わってくるので、臨機応変に考えるほうが良いと思います。

《PictCardについての仕様》と、《使用する側(API から取得したデータをPictCardに渡す側)のコード》を分離して考えて、以下が僕の意見です。

  1. PictCard の仕様として、filename が空のときに PictCard の一部が特定のフォールバック表示になる
    • 例: PictCard のうち、Image の内容が 「No Image」と書かれた画像 に置き換わる
    • filename: string | undefined にする
  2. PictCard の仕様として、filename が空になる場合は考えない。「仕様ではなく実装の都合」で null / undefined / 空文字列等を弾きたい
    • → 使用する側のコードで、filename が空なら PictCard そのものを描画しないよう絞り込む
2.の例
{picts.map(({ pictNo, filename }) => {
  if (pictNo == null) return null;
  if (filename == null || filename === "") return null; // バックエンド側の仕様は分かりませんが、とりあえず、空文字列も弾いておきます
  // picNo と filename がどちらも有効な値の場合にのみ、PictCardを描画します
  return (
    <PictCard
      key={pictNo} 
      // 省略しただけなら余計なお世話になりますが、
      // key にデータ由来のユニークな id を渡すことが推奨されています
      pictNo={pictNo}
      filename={filename}
    />
  );
})}

もしくは、zod, valibot のようなライブラリを使って、バックエンドから受け取ったデータを予め厳しく検証しておくことも可能です。

setsukosetsuko

コメントありがとうございます。

ご指摘の通り臨機応変に対応していくことも、keyに関しても存じ上げております。

この記事は、現場で言われたことを自分なりにまとめておきたかったのでコピペしたのですが
現場コードをまるっと写す訳にはいかないと思い、雑に書いてしまったところがあります、

育児となれない現場とで中々時間が取れない中
書く時間があまりにも無いため自分が分かればいいと思い読者さんのことを考えられておりませんでした、、
(非公開にしないのにも諸々理由があるので申し訳ないです。)

知識も経験の浅い私が中途半端に書いてしまった私の記事を読んだ方が誤解すると良くないので
今後は前書きやあとがき等に注意書きしようかと思います。

ご指摘ありがとうございました。

Honey32Honey32

いえいえ、こちらこそ記事だけ見てお節介なコメントをしてしまい、申し訳ないです💦

確かに、現場で学んだことをアウトプットするのは、うまく抽象化しないといけないので負担がすごいですよね...

大変な中とは思いますが、応援させていただきます