TL;DR

CSS-in-JS のライブラリの 1 つ。タグ付きテンプレートでスタイルを書き、スタイル付きの React コンポーネントを作る。実行時にスタイルを挿入する Runtime CSS-in-JS に分類される。

仕組み

styled には、HTML の各タグに対応するタグ関数が用意されている。この関数にスタイルを書いたタグ付きテンプレートを渡すと、そのスタイルを持つコンポーネントが返る。

styled-components はクラス名を自動で生成して要素に付け、スタイルのルールをページの <head> にある <style> に挿入する。クラス名は、セレクタに使う安定した sc-* と、スタイルのハッシュから作ったランダムな文字列の 2 つである。

<img src="..." class="sc-blHHSb gaBufi" />

関数の型パラメータに渡した型は、返されるコンポーネントの props になる。名前に $ を付けた props は、スタイルの計算にだけ使う一時的な props で、DOM の要素には渡されない。通常の CSS は静的だが、このスタイルは props が変わると自動で更新される。

既存のスタイル付きコンポーネントを styled に渡すと、元のスタイルに新しいスタイルを足したコンポーネントが返る。

例

import styled from "styled-components"
 
type ImgComponentProps = {
  $height?: string | number
}
 
const ScaledImg = styled.img<ImgComponentProps>`
  object-fit: cover;
  width: 100%;
  height: ${(props) => (typeof props.$height === "number" ? `${props.$height}px` : props.$height)};
`
 
// スタイルの拡張
const TestImg = styled(ScaledImg)`
  width: auto;
  object-fit: fill;
`

注意点

自動で付くクラス名は読みにくい。公式の Babel プラグイン babel-plugin-styled-components を使うと、CarouselSlide__ScaledImg- のようにモジュール名とコンポーネント名がクラス名に入り、デバッグしやすくなる。テンプレートリテラル内のコメントや余分な空白も削除されるので、パフォーマンスも上がる。公式も使うことを推奨している。Vite の React プラグインは内部で Babel を使っているので、設定に足すだけで導入できる。

export default defineConfig({
  plugins: [
    react({
      babel: {
        plugins: [["babel-plugin-styled-components", { displayName: true, fileName: true }]],
      },
    }),
  ],
})

スタイルを lint するには、Stylelint に postcss-styled-syntax を組み合わせる。このパーサが JavaScript や TypeScript の中のスタイルを解析する。

{
  "extends": ["stylelint-config-standard"],
  "customSyntax": "postcss-styled-syntax",
  "allowEmptyInput": true
}

テストでスタイルを確かめるには、jest-styled-components を使う。Vitest では test.setupFiles に指定したファイルで読み込めば、テストごとに import しなくてよい。toHaveStyleRule でスタイルをアサーションできる。

it("has the expected static styles", () => {
  render(<CarouselSlide />)
  const img = screen.getByRole("img")
  expect(img).toHaveStyleRule("object-fit", "cover")
  expect(img).toHaveStyleRule("width", "100%")
})

関連

出典