kk-web

props に className ってあんまり持たせないほうが良いんじゃないかなぁ

2020-11-13

と、個人的には思っています。

そもそも props に className を持たせなければいけないケースって、以下の 2 ケースくらいなのかなーと思っていて。

  1. 親コンポーネントからスタイルを流し込みたい
  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 を渡していることがあまりに多ければ、今一度ロジックを見直してみても損はないんじゃないかなーと思います。

© 2018 kk-web