【React】気づかず汎用コンポーネントを作ろうとしてしまった話


フロントエンドエンジニアの茶木です。
先日、テキストエリアにトリム機能をつけたコンポーネントを作っていたときに、つまづいたときの学びをまとめておこうと思います。

概要

テキストエリアは MUI ライブラリの TextArea コンポーネントを利用していて、TextArea の機能を保ったまま、onBlur 時にトリムを行う TrimableTextArea というコンポーネントを作成しようとしていました。ただし TextAreapropsdefaultValue は組み込むトリムの機能で使うのが難しいため、使えないようにしておく必要がありました。

というわけで、Omit を使って TextArea から defaultValue を排除したものを作りました。

type Props = Omit<ComponentProps<typeof TextArea>, "defaultValue">

export function TrimableTextArea(props: Props): ReactElement {
  :
}

なにが問題か?

動作するものはできたのですが、コードレビューでは、TextAreaの機能を把握した上で、動きを変えないように TrimableTextArea は差分をコントロールできているのかが怪しい点が挙げられました。

コードからは Omit で排除した defaultValue 以外は、TrimableTextArea に渡しても動くように実装済みと見えてしまうが、実際には担保されていないということです。TextArea の機能に委託されているので動くような気もしますが、TextAreaprops は多岐にわたり把握しきれていません。Omit を使うと、それらを検証なしに橋渡しできてしまいます。

しかし実際のところ、TrimableTextArea は トリム機能を提供するのが主眼で、TextArea の機能を継承する必要はありません。継承する必要があるのは、ライブラリのように汎化部品として作るときだけです。

解決方針

TextArea に含まれる、props が失われるのは承知の上で、Omit ではなく Pick を使うように書き換えました。これによって Pick していない TextAreaprops は使えなくなりましたが、同時に、Pick していない propsTrimableTextArea では未対応だとわかります。

type Props = Pick<ComponentProps<typeof TextArea>, "value" | "onChange">

export function TrimableTextArea({ value, onChange }: Props): ReactElement {
  :
  :
  return <TextArea value={value} onChange={onChange} /> 
}

たとえばこのコードでは TexaArea にある placeholder が継承されておらず使えないのですが、 TrimableTextArea でも placeholder が必要になったときに改めて Pick に加えて機能追加します。これは YAGNI の原則にも一致します。`

まとめ

汎用コンポーネントを作らない

MUI などが提供する基本機能に近いコンポーネントなどは、機能が多く(あまり使わない機能も含めて)内容を把握し、継承した新しいコンポーネントを作るのは大変です。また、全部を継承したコンポーネントが最初から必要なケースもあまりありません。

それでも継承した汎用コンポーネントを作ろうとするとデメリットが大きいです。それは、

  • すぐ必要でない機能は、きちんとテストされない
  • 問題があったときに、追加分か過去の作り込みが原因か切り分けの手間が発生する

明示的な props だけ使う

提供された機能を使って、コンポーネントを作る場合、すぐに使用しない機能を継承しないほうが良いです。また、継承元の型をのまま継承先で使ったり、TypeScript の Omit を使って継承元の型を変形して使うと未使用の props が明示的にならないので、未来に爆弾を抱え込むことになります。

同様に、スプレッド演算子 ... を使用して継承元に props を渡すと、渡している個々の prop の挙動を把握しないまま渡せてしまいます。lint で props にスプレッド演算子を禁止する設定があるのは、無思慮にこれを行えないようにする意味がありそうです。

おわりに

ライブラリのコンポーネントが美しく汎用的に作られているので、それを壊さないように汎用的なまま、機能を受け継いだコンポーネントを作りたい誘惑に駆られることはよくあるのですが、ひとたびコンポーネントを作ったら保守が発生するということを忘れないようにしようと思います。

関連

Gaji-Laboでは、React経験が豊富なフロントエンドエンジニアを募集しています

弊社ではReactの知見で事業作りに貢献したいフロントエンドエンジニアを募集しています。大きな制作会社や事業会社とはひと味もふた味も違うGaji-Laboを味わいに来ませんか?

もちろん、一緒にお仕事をしてくださるパートナーさんも随時募集中です。まずはお気軽に声をかけてください。お仕事お問い合わせや採用への応募、共に大歓迎です!

求人応募してみる!

投稿者 Chaki Hironori

webライターもやってるフロントエンドエンジニアです。Reactは自信があります。またデザイン畑の出身で、気持ちのいいアニメーションやインタラクティブな表現は丁寧に手掛けます。好きなものは中南米の遺跡で、スペイン語が少しできます。