CSS in JSとは何か
想定読者
- 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-propcss 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;
}
`;
利用するメリット
- カプセル化:
- スタイルはコンポーネントと紐づくため、関心の分離が行われる
マークアップとロジックを別々のファイルに書いて人為的に技術を分離するのではなく、React はマークアップとロジックを両方含む疎結合の「コンポーネント」という単位を用いて関心を分離します。
- ユニークなクラス名が自動生成されるため、定義したスタイルが他のコンポーネントやライブラリに影響を与えないことが担保される
- スタイルはコンポーネントと紐づくため、関心の分離が行われる
- メンテナンス性:
- CSS in JS のスコープ機能によって、ユニークなクラス名が自動生成され、スタイル定義は定義した対象のコンポーネントにのみ影響するため、他への影響を気にすることなく CSS を修正できる
- 細かい CSS 設計(セレクタ階層や命名規則)が不要になる
- 動的なスタイリング:
- CSS の変数や関数よりも、コンテキストに基づいた動的なスタイリングができる
- JavaScript の変数、関数と統合できる
- 移植性: コンポーネントには独自のスタイルがあるため、他のプロジェクトで簡単に共有または再利用できる
利用するデメリット
- 可読性:
- 自動生成されるクラス名が読めない:
<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)してしまえば良い
関連
注釈
大規模開発において、設計論を当てはめられないと技術的負債が生じてしまう。React hooks と宣言的 UI によって、設計論を当てはめやすくなる。 ↩