props に className ってあんまり持たせないほうが良いんじゃないかなぁ
と、個人的には思っています。
そもそも props に className を持たせなければいけないケースって、以下の 2 ケースくらいなのかなーと思っていて。
- 親コンポーネントからスタイルを流し込みたい
- 親コンポーネントから特定の class 名に対する操作を行いたい
ただ、そもそも 2 のケースについては ref で解決できることがほとんどだと思います。
どうしても className じゃないとダメ!みたいなケースって、少なくとも自分は見たことがありません。
で、自分の過去の経験ではほとんどが 1 のケースでして。
要するに抽象的な汎用コンポーネントを作成して、そのコンポーネントの親コンポーネントにスタイリングを持たせたい、みたいな感じなんですよね。
実際に過去出会ったコンポーネントを具体的に書くと。
import React, { FC } from "react";
export type ButtonProps = Pick<ComponentPropsWithoutRef<"button">, "className">;
const Button: FC<ButtonProps> = ({ children, className }) => (
<button className={className}>{children}</button>
);
export default Button;
import React, { FC } from "react";
import Button from "./Button";
import styles from "./style.module.css";
const Hoge: FC = () => (
<div>
<p>fugafuga</p>
<Button className={styles.piyo}>moge</Button>
</div>
);
.piyo {
margin: 0;
}
ぱっとみ良さげに見えますが、個人的に className を props で流し込むのってアンチパターンな気がします。
上記の例で言うと、そもそも以下のように書けば済む話なわけで。
import React, { FC } from "react";
const Button: FC = ({ children }) => <button>{children}</button>;
export default Button;
import React, { FC } from "react";
import Button from "./Button";
import styles from "./style.module.css";
const Hoge: FC = () => (
<div>
<p>fugafuga</p>
<div className={styles.piyo}>
<Button>moge</Button>
</div>
</div>
);
あくまで個人的にですが、こっちのほうがなんぼか綺麗でロジカルだよなーと。
そうなってくると「じゃあボタンの色を変えたい場合はどうすれば良いんだよ!」みたいな反論も出てくると思うんですよね。
例えば以下のような感じですかね?
import React, { FC } from "react";
import Button from "./Button";
import styles from "./style.module.css";
const Hoge: FC = () => (
<div>
<p>fugafuga</p>
<Button className={styles.piyo}>moge</Button>
<Button className={styles.piyopiyo}>mogemoge</Button>
</div>
);
.piyo {
background: red;
}
.piyopiyo {
background: blue;
}
気持ちはわからなくもないです。
ただ、自分だったらこう書いちゃうかなーと。
import React, { FC } from "react";
import styles from "./style.module.css";
const PiyoButton: FC = ({ children }) => (
<button className={styles.piyo}>{children}</button>
);
export default PiyoButton;
import React, { FC } from "react";
import styles from "./style.module.css";
const PiyoPiyoButton: FC = ({ children }) => (
<button className={styles.piyopiyo}>{children}</button>
);
export default PiyoPiyoButton;
単純に別コンポーネントにしちゃえ!って感じです。
と、ここまで書いても「じゃあ 100 色のボタンコンポーネントがあったら 100 個作るのかよ!」とか「色以外全部同じだと効率わるいだろ!」とか反論を食らいそうですが。
言ってしまえばケースバイケースなわけで、もしかしたら Button コンポーネントの中で条件分岐を挟むときもあると思います。
ただ、少なくとも親コンポーネント側で子コンポーネントのスタイリングを持たせることはないです、ロジック的にナンセンスかなと。
過去何度も書いてきましたが、コンポーネント設計においては、個人的に低レイヤーのコンポーネントであったとしても、なるべく抽象的なコンポーネントは切るべきではないと思っています。
コンポーネント設計における『汎用性』というのは『自由度が高い』という意味ではないと思っていて。
むしろ『自由度が低い』、つまり『具体的である』から『汎用性が高い』コンポーネントになり得ると思っています。
従って、props から className が受け取れるようなコンポーネントは『自由度が高い』ため、抽象的なコンポーネントである以上、できるだけ作成を控えるべきかなーと。
抽象的なコンポーネントは、親コンポーネント側で好き勝手にカスタマイズができてしまうため、そもそも作成する価値が薄くなってしまうケースが多いです。
UI / UX を問わないロジック重視なコンポーネントであれば作成する形が高いケースも存在しますが、最近は hooks などでなんとかなってしまうので、作成する価値自体は下がっているかなーと。
ただし npm パッケージ化されているコンポーネントについては話は別で。
使用用途によって親コンポーネントからスタイリングを渡したくなるケースは多いため、どちらかといえば渋々 className を受け付けているのかなと。
とはいえ、npm パッケージ化されているコンポーネントについても、基本はスタイルを崩さないように使うのがベターかなーとは思いますが。
再度書きますが、className を props として流し込むケースって、基本はアンチパターンだと思います。
特に AtomicDesign においては、親コンポーネント側で子のスタイリングを持つケースは存在しないのかなーと、多分。
色やフォント情報についてはなるべく低レイヤーで確定させ、余白などは中〜高レイヤーで切ってる、それだけなのかなーと。
もちろん全てが全て綺麗に落とし込めるとは思っていません。
が、もし props に className を渡していることがあまりに多ければ、今一度ロジックを見直してみても損はないんじゃないかなーと思います。