想定読者
- JS フレームワークを利用して開発したことがある人
- CSS in JS について概要を知りたい人
React を始めとする昨今の JS フレームワークには下記のような特徴がある。
React や Vue.js が目新しい技術だから利用しているのではなく、これらが持つコンポーネントや宣言的 UI といった特徴に価値を見出しているため、React や Vue.js を採用している。
宣言的 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 と言う(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 が劣っていて、宣言的 UI が優れているという話ではない。
スケールする設計が求められる大規模開発において、宣言的 UI が有用であるという話である1。
そのため、LP や小規模なページの実装をする際に宣言的 UI を採用することがオーバースペックになりうるケースも当然ある。
CSS in JS とは、外部ファイルでスタイルを定義するのではなく、JavaScript を用いて CSS を記述するアプローチのことを指す。
CSS in JS は、コンポーネントに属する CSS 定義をバンドルするライブラリである。CSS in JS を利用することで、CSS はコンポーネントに定義され、外部の CSS ファイルに依存することなく、コンポーネント単体で独立して動作する。
グローバルな CSS を利用している場合、CSS の定義を変更した際にどこへ影響があるか分かりづらい(CSS 設計が必要になる)。CSS in JS を利用することで、あるコンポーネントの CSS 定義を変更しても他のコンポーネントへの影響がなくなる。
下記はのコード例は 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 については、以下のような機能を提供している。
…など。
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/styledconst 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>
import { Global, css } from "@emotion/react";
class App extends React.Component {
render() {
return (
<Global
styles={css`
body {
padding: 0;
margin: 0;
}
`}
/>
);
}
}
import styled from "@emotion/styled";
const Button = styled.button`
background-color: ${(props) => (props.primary ? "#007bff" : "#6c757d")};
`;
import styled from "@emotion/styled";
const Anchor = styled(Button.withComponent("a"))`
text-decoration: none;
`;
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);
}
`;
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}
`;
// 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>
);
}
}
const Card = styled.div`
background-color: #6c757d;
`;
const Container = styled.div`
height: 100%;
width: 100%;
${Card} {
background-color: blue;
}
`;
マークアップとロジックを別々のファイルに書いて人為的に技術を分離するのではなく、React はマークアップとロジックを両方含む疎結合の「コンポーネント」という単位を用いて関心を分離します。
<div class="sc-cNKqjZ bWaaQa"></div>
const Box = styled.div({
background: "#fff",
height: "50px",
width: "50px",
});
const Button = styled.button`
background: #fff;
height: 50px;
width: 50px;
`;
大規模開発において、設計論を当てはめられないと技術的負債が生じてしまう。React hooks と宣言的 UI によって、設計論を当てはめやすくなる。 ↩