💅

Styled Componentsを無闇に使わないで

2022/06/05に公開
3

Styled ComponentsはCSS in JSの先駆けとなり、この手のツールではトップクラスの人気である覚えがあります。ですが、実のところあまり濫用されて欲しくない気持ちを抱いています。

特記事項

  • CSSやReactの初学者向けではありません。
  • この記事は以下の是非を問うものではありません。
    • CSS in JS / Plain CSS
    • CSSファイルを分離する / 分離しない
    • テンプレートリテラル / Object Style
    • CSSコードのスタイル

はじめに

Styled Componentsについて

Styled Componentsというライブラリは、styled.div...のようなAPIを持つCSS in JSライブラリです。これが開発された後、Emotion・Stitchesといった後発のライブラリでも同じ仕組みが実装されました。

今回Styled Componentsと呼ぶものは、ライブラリ名でなくその仕組みを持ったAPIの方になります。つまりEmotionでstyled...と書いていても当てはまることです。(以下、場合によりSCと略記します)

なお筆者はEmotionがメインなので、ソースコードのサンプルはEmotionがベースになっていますが他のパッケージでも大方は変わらないと思います。

どんなAPIなのか

Emotionのドキュメントから拝借したコードです。愛されているポイントといえば、「スタイルだけ持ったコンポーネントを楽に作りたい!」という特徴でしょう。実際にかなり楽です。

import styled from '@emotion/styled'

const Button = styled.button`
  color: turquoise;
`

render(<Button>This my button component.</Button>)

しかし楽であるがゆえに色々なものを覆い隠しすぎており、プログラマーの成長を妨げるような代償があります。

なぜ無闇に使って欲しくないのか

Styled Componentsは簡単すぎる

題材は色を変えられるシンプルなボタンコンポーネントとします。SCで再現するとこのようになるでしょう。

type Props = ComponentProps<'button'> & {
  color: 'base' | 'primary';
};

const colorStyle = (props: Props) => {
  if (props.color === 'base') {
    return css`
      background-color: lightgray;
      color: black;
    `;
  }

  if (props.color === 'primary') {
    return css`
      background-color: #0070f3;
      color: white;
    `;
  }
};

export const Button = styled.button<Props>`
  height: 40px;
  padding: 0 16px;
  border-radius: 4px;
  ${colorStyle}
`;

Button.displayName = 'Button';

そして利用側は以下のようになります。

<Button color="base">Base</Button>
<Button color="primary">Primary</Button>

SCでスタイリングしたコンポーネントは名の通りスタイルだけを施したコンポーネントであり、自由にボタンとしてのパラメータは変化なく使えます。例えば以下のようなものです。

  • [a] 元々button要素に備わったprops(onClickなど)
  • [b] classNameで更にスタイルを追加する

しかしこれらの特徴は一切SCで実装した定義には現れていません。では代わりにCSSモジュールでやってみましょう。(CSSファイルは省略)

Styled Componentsは初歩の構造を隠蔽する

type Ref = HTMLButtonElement;

type Props = ComponentProps<'button'> & {
  color: 'base' | 'primary';
};

export const Button = React.forwardRef<Ref, Props>((props, ref) => {
  const { color, className, ...rest } = props;

  return (
    <button
      ref={ref}
      className={cx(styles.root, styles[`root__color_${color}`], className)}
      {...rest}
    />
  );
});

Button.displayName = 'Button';

色々隠れていたところが顕になりました。上記のコードはReactを初歩から積み上げているなら普通の処理に見えて、場当たり的にSCだけ覚えてコーディングしていた場合はさぞかし見慣れないでしょう。

ここで前述のコードに解説を入れます。

// React.forwardRefでラップする構文を使い、refを受け取れるようにする
export const Button = React.forwardRef<Ref, Props>((props, ref) => {
  // classNameを追加でマージできるようにする (1)
  const { color, className, ...rest } = props;

  return (
    <button
      ref={ref}
      // classNameを追加でマージできるようにする (2)
      className={cx(styles.root, styles[`root__color_${color}`], className)}
      // button要素が持つpropsでclassName以外のものは、そのまま受け流す
      {...rest}
    />
  );
});

少し前に述べた[a][b]はReactの機能を使ってこれだけの処理を施すことが必要なのです。

Styled Componentsはそれらの必要性を影も形もないように覆い隠してしまいます。どこかで歪な知識の開発者が増えるのも時間の問題ではないでしょう。

関連する実装方法については以下の記事で詳しく述べられています。(コードスタイル以外は同じポイントです) UI層をあまり触らない人にとっては疎かにしている可能性が高いと思われますが、この内容は大事なことです。

https://zenn.dev/takepepe/articles/controlleable-of-atoms

Styled Componentsは独特である

Styled Componentsは独特です。巷では「CSS vs CSS-in-JS(+Tailwind)」という区分けが議題に上がりますが、それよりも「Styled Components vs それ以外」という視点もかなり大切だと思っています。

なぜか。これは「styled APIによるスタイリング vs propsによるスタイリング」と言い換えられるからです。実際に整理してみると...

  • className=... (多くのパターン)
  • css=... (Emotion等で見られるcss prop。JSXを魔改造する)
  • styled.div`` (Styled Componentなパターン。タグとスタイルは結びついている)

といった具合になります。なので前述のCSS Modulesの例えで出したソースコードはPlain CSSで書こうがTailwindでクラスを賄おうが大枠は同じ構造になります。なので後者に寄せて、前者は明確な目的を持って使った方が好ましいかもしれません。(例: 短期間でUIライブラリを量産するとき)

CSS in JavaScript黎明期は、JavaScriptによるスタイル管理とstyled APIの困難さがよく混ぜて語られてきました。しかし蓋を開けると要点は後者だった訳です。

どんなときに使って欲しいのか

高度なUIライブラリにはStyled Componentsで構築されたもの(NextUI)やそうでないもの(Chakra UI)もあり、結局の所この手のライブラリは好みで決めるしかない。ではどういった好みを重視するかは

  • Reactを分かっているメンバー体制 (Styled Componentがやっている隠蔽とそうでないときの違いを認識している)
  • Styled Componentsのメリットとデメリットを明確に整理でき、それらの対処法がある (例: タグ名を用意に連想できるようなコンポーネント命名ができるか?)

という状況のときに限ると思う。正直決まったボイラープレートを減らしたいという欲求は誰にでもあり、充分その目的を認識した上でStitchesなら個人的に使ってみたい。

Discussion

Kaido IwamotoKaido Iwamoto

個人的には、styled-components以外でもdata-属性を使ってSCっぽさを取り入れた書き方をするのが好きです。
次のような感じです。クラス名をいくつも組み合わせるよりもすっきり書ける印象があります。

export const C: FC<{kind: 'a' | 'b'}> = ({kind}) =>
  <div className='box' data-kind={kind}></div>
.box[data-kind=a] {
  background: red;
}

.box[data-kind=b] {
  background: blue;
}
yhayha

コメントありがとうございます!data属性は文字列結合をしなくてよく私としてもエレガントに感じますが、今回のテーマではなるべく簡素な方に寄せたいので省略させていただきました。

kona(coffee)kona(coffee)

突然の投稿すみません。
初めまして、スッキリシンプルで綺麗ですね。
参考にさせて頂きます。