【React】気づかず汎用コンポーネントを作ろうとしてしまった話
フロントエンドエンジニアの茶木です。
先日、テキストエリアにトリム機能をつけたコンポーネントを作っていたときに、つまづいたときの学びをまとめておこうと思います。
概要
テキストエリアは MUI ライブラリの TextArea コンポーネントを利用していて、TextArea
の機能を保ったまま、onBlur
時にトリムを行う TrimableTextArea
というコンポーネントを作成しようとしていました。ただし TextArea
の props
の defaultValue
は組み込むトリムの機能で使うのが難しいため、使えないようにしておく必要がありました。
というわけで、Omit
を使って TextArea
から defaultValue
を排除したものを作りました。
type Props = Omit<ComponentProps<typeof TextArea>, "defaultValue">
export function TrimableTextArea(props: Props): ReactElement {
:
}
なにが問題か?
動作するものはできたのですが、コードレビューでは、TextAreaの機能を把握した上で、動きを変えないように TrimableTextArea
は差分をコントロールできているのかが怪しい点が挙げられました。
コードからは Omit
で排除した defaultValue
以外は、TrimableTextArea
に渡しても動くように実装済みと見えてしまうが、実際には担保されていないということです。TextArea
の機能に委託されているので動くような気もしますが、TextArea
の props
は多岐にわたり把握しきれていません。Omit
を使うと、それらを検証なしに橋渡しできてしまいます。
しかし実際のところ、TrimableTextArea
は トリム機能を提供するのが主眼で、TextArea
の機能を継承する必要はありません。継承する必要があるのは、ライブラリのように汎化部品として作るときだけです。
解決方針
TextArea
に含まれる、props
が失われるのは承知の上で、Omit
ではなく Pick
を使うように書き換えました。これによって Pick
していない TextArea
の props
は使えなくなりましたが、同時に、Pick
していない props
は TrimableTextArea
では未対応だとわかります。
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を味わいに来ませんか?
もちろん、一緒にお仕事をしてくださるパートナーさんも随時募集中です。まずはお気軽に声をかけてください。お仕事お問い合わせや採用への応募、共に大歓迎です!
求人応募してみる!