CSS in JS

11 min read
hiroweb developer

想定読者

  • JS フレームワークを利用して開発したことがある人
  • CSS in JS について概要を知りたい人

そもそも JS フレームワーク(React、Vue.js)を採用する理由とは何か

React を始めとする昨今の JS フレームワークには下記のような特徴がある。

  • 宣言的 UI(テンプレーティング)
  • データバインディング(data → UI)
  • コンポーネント化
  • 高効率レンダリング(仮想 DOM)

React や Vue.js が目新しい技術だから利用しているのではなく、これらが持つコンポーネントや宣言的 UI といった特徴に価値を見出しているため、React や Vue.js を採用している。

「命令的 UI」 と 「宣言的 UI」

宣言的 UI の誕生以前は、手続き的(命令的)にコードを記述する必要があった。

命令的 UI

どのようにして欲しいか定義していくものを命令的 UI と言う(HOW)。

以下、jQuery による DOM 操作の例。

<ul></ul>
<button type="button">Append</button>
$("button").on("click", function () {
  const animals = ["子", "丑", "寅"];

  $("ul").empty();

  animals.map((animal) => {
    $("ul").append(`<li>${animal}</li>`);
  });
});

宣言的 UI

何をしたいかを定義していくものを宣言的 UI と言う(WHAT)。

以下、React による実装例。

import React, { useState } from "react";

export default function App() {
  const [animals, setAnimals] = useState([]);

  const append = () => {
    const animals = ["子", "丑", "寅"];

    setAnimals(animals);
  };

  return (
    <>
      <ul>
        {animals.map((animal, index) => {
          return <li key={index}>{animal}</li>;
        })}
      </ul>
      <button type="button" onClick={append}>
        Append
      </button>
    </>
  );
}

「命令的 UI」 VS 「宣言的 UI」

命令的 UI が劣っていて、宣言的 UI が優れているという話ではない。

スケールする設計が求められる大規模開発において、宣言的 UI が有用であるという話である1
そのため、LP や小規模なページの実装をする際に宣言的 UI を採用することがオーバースペックになりうるケースも当然ある。

CSS in JS とは

CSS in JS とは、外部ファイルでスタイルを定義するのではなく、JavaScript を用いて CSS を記述するアプローチのことを指す。

CSS in JS が解決する課題

CSS in JS は、コンポーネントに属する CSS 定義をバンドルするライブラリである。CSS in JS を利用することで、CSS はコンポーネントに定義され、外部の CSS ファイルに依存することなく、コンポーネント単体で独立して動作する。

グローバルな CSS を利用している場合、CSS の定義を変更した際にどこへ影響があるか分かりづらい(CSS 設計が必要になる)。CSS in JS を利用することで、あるコンポーネントの CSS 定義を変更しても他のコンポーネントへの影響がなくなる。

CSS in JS ではないアプローチ

下記はのコード例は JavaScript 上でスタイルを定義しているが、単なるインラインスタイルであるため、CSS in JS とは呼ばれない。

// JavaScript
const div = document.querySelector("div");
div.style.background = "#fff";
div.style.height = "50px";
div.style.width = "50px";

// jQuery
$("div").css({
  background: "#fff",
  height: "50px",
  width: "50px",
});

// React
const styles = {
  background: "#fff",
  height: "50px",
  width: "50px",
};

const Component = () => <div style={styles}></div>;

CSS in JS ライブラリ

Emotion の使い方

CSS in JS ライブラリは、さまざまな機能を標準で備えている。

Emotion については、以下のような機能を提供している。

  • グローバルセレクター
  • ベンタープレフィックスの自動付与
  • セレクターのネスト
  • メディアクエリ
  • キャッシュ
  • アニメーションの組み込み
  • CSR、SSR 対応

…など。

記法

Emotion の場合、以下のような記述でスタイル定義ができる(他の CSS in JS でも同様の記法が多い)。

オブジェクトスタイル記法

const Box = styled.div({
  background: "#fff",
  height: "50px",
  width: "50px",
});

タグ付きテンプレートリテラル記法

const Button = styled.button`
  background: #fff;
  height: 50px;
  width: 50px;
`;

定義

それぞれ、オブジェクトスタイル記法とタグ付きテンプレートリテラル記法を用いて定義ができる。

  • @emotion/styled: https://emotion.sh/docs/styled
    styled-components と同等の記法が使える
    const Button = styled.button`
      background: #fff;
      height: 50px;
      width: 50px;
    `;
    
  • @emotion/react: https://emotion.sh/docs/css-prop
    css prop を利用した記法
    <button
      css={css`
        background: #fff;
        height: 50px;
        width: 50px;
      `}
    ></button>
    

GlobalStyle

import { Global, css } from "@emotion/react";

class App extends React.Component {
  render() {
    return (
      <Global
        styles={css`
          body {
            padding: 0;
            margin: 0;
          }
        `}
      />
    );
  }
}

props を適用する

import styled from "@emotion/styled";

const Button = styled.button`
  background-color: ${(props) => (props.primary ? "#007bff" : "#6c757d")};
`;

component の要素を変更する

import styled from "@emotion/styled";

const Anchor = styled(Button.withComponent("a"))`
  text-decoration: none;
`;

animation

import { css, keyframes } from "@emotion/react";

class App extends React.Component {
  render() {
    return (
      <Button
        styles={css`
          animation: ${bounce} 1s ease infinite;
          transform-origin: center bottom;
        `}
      />
    );
  }
}

const bounce = keyframes`
  from, 20%, 53%, 80%, to {
    transform: translate3d(0,0,0);
  }

  40%, 43% {
    transform: translate3d(0, -30px, 0);
  }

  70% {
    transform: translate3d(0, -15px, 0);
  }

  90% {
    transform: translate3d(0,-4px,0);
  }
`;

mixin

import styled from "@emotion/styled";
import { css, keyframes } from "@emotion/react";

const CardStyle = css`
  background-color: white;
  border-radius: 8px;
  padding: 16px;
`;

const Input = styled.input`
  ${CardStyle}
`;

ThemeProvider

// theme.ts
const theme = {
  primary: "#007bff",
  secondary: "#28a745",
};

export default theme;
import { ThemeProvider } from "@emotion/react";
import theme from "./theme";

const Button = styled.button`
  background-color: ${(props) => props.theme.secondary};
`;

class App extends React.Component {
  render() {
    return (
      <ThemeProvider theme={theme}>
        <Button />
      </ThemeProvider>
    );
  }
}

Nesting

const Card = styled.div`
  background-color: #6c757d;
`;

const Container = styled.div`
  height: 100%;
  width: 100%;

  ${Card} {
    background-color: blue;
  }
`;

利用するメリット

利用するデメリット

  • 可読性:
    • 自動生成されるクラス名が読めない:
    <div class="sc-cNKqjZ bWaaQa"></div>
    
    • CSS in JS の記法が独特:(ライブラリに依る)
      • オブジェクトスタイル
      const Box = styled.div({
        background: "#fff",
        height: "50px",
        width: "50px",
      });
      
      • タグ付きテンプレートリテラル
      const Button = styled.button`
        background: #fff;
        height: 50px;
        width: 50px;
      `;
      
  • 移植性: CSS → CSS in JS への移植は難しいケースがある
  • 学習曲線: CSS には慣れていても、CSS-in-JS を使いこなすための学習コストは少なからず存在する
  • パフォーマンス: CSS in JS の黎明期は、パフォーマンスは課題として挙げられていたが、現在はさほど問題視されていない
    • SSR(Server Side Rendering)してしまえば良い

関連

注釈

  1. 大規模開発において、設計論を当てはめられないと技術的負債が生じてしまう。React hooks と宣言的 UI によって、設計論を当てはめやすくなる。