2024.09.04
ReactでのCSSはどうすればいい?スタイリング方法を色々実践してみた。
はじめに
こんにちは。木村です。
今回React(+TypeScript)にて開発を進める必要がありまして、ReactってどうやってCSS適用するんだ?ベストプラクティスってあるの?と、疑問点が多くありました。
これからReactを始めようとする人で、どのようなスタイリング手法を採用しようか迷われている方は多いと思います。
おそらくこのブログを見ていただいている方もそういうお悩みの方もいると思います。
最後のまとめにも書いていますが、最終的には、どの手法を採用するかは、プロジェクトの要件、メンバーのスキル、メンバーの好み、プロジェクト(アプリケーション)の規模、将来的な拡張性/保守性などによってしまうので、決定的に「これがいい」というのは無い、という結論に至りました。
なので、まずはざっとそれぞれの手法を調べてみて、使ってみることにしました。
自分なりに調べてみたメモ程度の考察ですが、ご参考になればと思います。
1. Pure CSS
従来のCSSファイルを使ってスタイリングを行う方法。スタイルシートをグローバルに適用し、HTMLやJSX内でクラス名を指定してスタイルを適用する。この方法は従来のCSSの知識をそのまま活用できるため、学習コストが低め。
しかし、CSSがグローバルに適用されるため、クラス名の衝突が起こりやすい。大規模なアプリケーションでは、スタイルの管理が難しくなり、保守性が低下する可能性がある。
style.css
.myClass {
color: blue;
font-size: 20px;
padding: 10px;
border: 1px solid black;
}
PureCssComponent.jsx
import './styles.css';
function PureCssComponent() {
return <div className="myClass">Pure CSSです。</div>;
}
export default PureCssComponent;
プレビュー
2. Sass/SCSS
CSSの拡張版であり、ネスト、変数、ミックスイン、継承など、CSSにない多くの強力な機能を備える。スタイルシートの再利用性や可読性が向上し、大規模なプロジェクトでも効率的にスタイルを管理しやすい。
生成されるCSSは、従来のCSSと同じくグローバルスコープに適用されるため、クラス名が競合するリスクがある。ただし、Sass/SCSSの機能を活用することで、クラス名の競合を回避するための工夫がしやすくなり、より構造化されたスタイルの設計が可能。
styles.scss
.myClass {
color: blue;
font-size: 20px;
padding: 10px;
border: 1px solid black;
}
SassComponent.jsx
import './styles.scss';
function SassComponent() {
return <div className="myClass">Sass/SCSSです。</div>;
}
export default SassComponent;
3. CSS Modules
各コンポーネント単位でCSSをスコープするための手法。
CSSファイルをモジュール化し、各コンポーネントに限定してスタイルを適用することで、名前衝突を回避できる。書き方は従来のCSSに非常に近く、スタイルシートを作成する感覚で利用できまるが、コンポーネントごとにスタイルが分離されているため、グローバルなスタイルの競合が発生しない。Pure CSSの使いやすさを保ちつつ、CSS-in-JSのようにコンポーネント単位でのスタイリングを実現する手法と言える。
将来的に非推奨になる、といった噂もあるが、確定的な情報はない。
styles.module.css
.CssModulesClass {
color: blue;
font-size: 20px;
padding: 10px;
border: 1px solid black;
}
CssModulesComponent.jsx
import styles from './styles.module.css';
function CssModulesComponent() {
return <div className={styles.CssModulesClass}>CSS Modulesです。</div>;
}
export default CssModulesComponent;
プレビュー
4. CSS-in-JS
JavaScript内でCSSスタイルを定義し、それをコンポーネントに適用する手法の総称。従来のCSSやSassのように別ファイルでスタイルを管理するのではなく、スタイルをJavaScriptファイル内に直接記述する。コンポーネント単位でスタイルを適用できるため、クラス名の衝突を回避しやすくなる。また、スタイルを動的に変更したり、テーマを適用したりすることも容易にできる。
CSS-in-JSには複数のライブラリ(例えば、Styled Components、Emotion、JSSなど)が存在し、それぞれのライブラリには特徴があり、採用する手法によってメリットとデメリットが異なるため、プロジェクトの要件やチームのスキルセットに応じて適切なライブラリを選択することが重要。
Inline Styles
こちらはライブラリではなく、JavaScriptオブジェクトを使用してスタイルを適用する記法。各スタイルをオブジェクトとして定義し、コンポーネント内の要素に直接適用する。この方法は、動的にスタイルを変更できる柔軟性もあるが、大規模なアプリケーションではスタイル管理が複雑になりやすい。
また、Inline Stylesは擬似クラス(例: :hover や :focus)やメディアクエリを直接サポートしていないので、従来のCSSに比べて一部の機能が制限される点に注意が必要。
InlineStylesComponent.jsx
function InlineStylesComponent() {
const style = {
color: 'blue',
fontSize: '20px',
padding: '10px',
border: '1px solid black',
};
return <div style={style}>Inline Stylesです。</div>;
}
export default InlineStylesComponent;
プレビュー
Styled JSX
Styled JSXはNext.jsを提供しているVercel(旧Zeit)が開発しているライブラリ。そのため、Next.jsとの統合が非常にスムーズで、特別な設定なしで使用できる。
記述方法は、スタイルをコンポーネント内の<style jsx>タグ内で記述する。この方法は、従来のHTMLにおける<style>タグに似ており、HTMLやCSSの知識を持つ開発者にとって直感的。
また、各コンポーネントごとに自動的にスコープを管理するため、グローバルなスタイルの競合を防いでくれる。スタイルはコンポーネント単位で閉じ込められるため、特別な設定をせずに安全なスタイリングが可能。
StyledJsxComponent.jsx
function StyledJsxComponent() {
return (
<div>
<p>Styled JSXです。</p>
<style jsx>{`
p {
color: blue;
font-size: 20px;
padding: 10px;
border: 1px solid black;
}
`}</style>
</div>
);
}
export default StyledJsxComponent;
プレビュー
Styled Components
Styled Componentsは、テンプレートリテラルを使用してスタイルを定義する。これにより、JavaScript内でCSSのように記述でき、CSSのフレキシビリティを保持しながらJavaScriptの利点も活用できる。
また、CSSの機能を完全にサポートしており、擬似クラスやメディアクエリなども簡単に適用できる。CSSに長けている開発者は違和感なく利用できる。
デメリットとしては、ある程度の学習が必要であることと、大規模なアプリケーションではランタイムにスタイルを生成するため、パフォーマンスに影響が出る可能性がある点が挙げられる。ただし、最新のバージョンではパフォーマンスの改善が行われており、適切に使用すれば多くのプロジェクトで有効。
StyledComponents.jsx
import styled from 'styled-components';
const StyledDiv = styled.div`
color: blue;
font-size: 20px;
padding: 10px;
border: 1px solid black;
`;
function StyledComponentsComponent() {
return <StyledDiv>Styled Componentsです。</StyledDiv>;
}
export default StyledComponentsComponent;
プレビュー
Emotion
Emotionは、軽量でパフォーマンスにも優れている。機能の豊富さを考慮してもコンパクトなバンドルサイズ。styledとcss の2つのスタイル適用方法を提供しており、これにより異なるニーズに応じたスタイル管理が可能。またテンプレートリテラル記法とオブジェクトスタイル記法の両方をサポートしており、柔軟にスタイルを記述することができる。
styled はコンポーネントベースでスタイルを定義するのに適しており、css はスタイルをオブジェクトや文字列として定義して複数のクラスを組み合わせる際に便利。これにより、必要に応じて異なるアプローチを使い分けることができる。さらに、EmotionはReact専用ではなく、他のフレームワークや環境でも使用できる柔軟性がある。
また、高度な機能を提供する一方、Styled Components同様に、ある程度の学習が必要。
EmotionComponent.jsx
/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react';
const style = css`
color: blue;
font-size: 20px;
padding: 10px;
border: 1px solid black;
`;
function EmotionComponent() {
return <div css={style}>Hello, Emotion!</div>;
}
export default EmotionComponent;
プレビュー
JSS
JSSはプラグインを通じた拡張性に特化しており、開発者が独自のカスタマイズを行いやすい。また、Reactや他の特定のフレームワークに依存しておらず、幅広いJavaScript環境で使用することが可能。特にオブジェクトベースのスタイル定義をサポートし、動的なスタイリングやテーマの適用が必要なプロジェクトに強い。
ただし、ランタイムでスタイルを生成するため、大規模なアプリケーションではパフォーマンスに若干の懸念がある点や、オブジェクトベースの記法に慣れるための学習コストが比較的高い点がデメリット。
JssComponent.jsx
import { createUseStyles } from 'react-jss';
const useStyles = createUseStyles({
myClass: {
color: 'blue',
fontSize: '20px',
padding: '10px',
border: '1px solid black',
},
});
function JssComponent() {
return <div className="myClass">JSSです。</div>;
}
export default JssComponent;
プレビュー
Goober
基本的な機能を提供しつつ、バンドルサイズを最小限に抑えられる。また、他の重厚なCSS-in-JSと比較してもパフォーマンスにも優れており、学習コストも低い。擬似クラスやメディアクエリのサポート、テーマの切り替えなど、一般的なスタイリングには十分対応しているものの、高度なカスタマイズを要求するアプリケーションには不向きな場合がある。
GooberComponent.jsx
import React from 'react';
import { styled, setup } from 'goober';
import { createElement } from 'react';
// Reactとの統合を設定
setup(createElement);
const MyDiv = styled('div')`
color: blue;
font-size: 20px;
padding: 10px;
border: 1px solid black;
`;
function GooberComponent() {
return <MyDiv>Gooberです。</MyDiv>;
}
export default GooberComponent;
プレビュー
Aphrodite
Facebookが開発した軽量のCSS-in-JSライブラリ。パフォーマンスに優れ、特に高速なスタイルシートの生成と適用が可能。機能がシンプルであるため、基本的なスタイリングには向いているが、凝ったスタイリングや高度なカスタマイズにはやや不向き。
AphroditeComponent.jsx
import { StyleSheet, css } from 'aphrodite';
const styles = StyleSheet.create({
myClass: {
color: 'blue',
fontSize: '20px',
padding: '10px',
border: '1px solid black',
},
});
function AphroditeComponent() {
return <div className={css(styles.myClass)}>Aphroditeです。</div>;
}
export default AphroditeComponent;
プレビュー
5. CSSフレームワーク
CSSフレームワークは、デザインとレイアウトを迅速に構築するためのスタイルシートのセットを提供する。これらのフレームワークは、あらかじめ定義されたスタイルやレイアウトを提供し、開発者が一からCSSを書かずに一貫したデザインを作成できるようにします。
Bootstrap
ウェブサイトやウェブアプリケーションのデザインを迅速に構築するためのCSSフレームワーク。グリッドシステム、レスポンシブデザイン、プリビルトのUIコンポーネント(ボタン、フォーム、ナビゲーションバーなど)を提供する。ただし、Reactで利用する場合はReact Bootstrapというのがあるので、そちらのほうが良いのかも。
BootstrapComponent.jsx
import React from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
function BootstrapComponent() {
return (
<div className="text-primary fs-4 p-3 border border-dark">
Bootstrapです。
</div>
);
}
export default BootstrapComponent;
プレビュー
Tailwind CSS
ユーティリティファーストのCSSフレームワーク。個別のクラスを直接HTMLに適用することで、迅速にスタイルをカスタマイズできる。デザインの一貫性を保ちながら、CSSを直接書く感覚でスタイルを細かく調整でき、複雑なデザインもシンプルに実現できる。た、不要なスタイルを削除してバンドルサイズを最適化する機能(Purging)も備えており、パフォーマンスにも優れている。
TailwindCssComponent.jsx
import React from 'react';
function TailwindComponent() {
return (
<div className="text-blue-500 text-xl p-2 border border-black">
Tailwind CSSです。
</div>
);
}
export default TailwindComponent;
プレビュー
6. UIライブラリ
ユーザーインターフェース(UI)を構築するために再利用可能なコンポーネントやスタイルを提供する。開発者が迅速かつ一貫したデザインでウェブアプリケーションやモバイルアプリケーションを作成でき、開発効率を大幅に向上させることができる。
ただし、バンドルサイズも大きくなりがちで、パフォーマンスやデザインの柔軟性を求められる場合には対策を考慮して利用する必要がある。
Material-UI (MUI)
GoogleのMaterial Designガイドラインに基づいたReactコンポーネントライブラリ。多くのスタイル付きコンポーネントが用意されており、ReactアプリケーションのUIを迅速に構築できる。MUIは、CSSを適用するのではなく、既にデザインされたUIコンポーネントを活用して、統一感のあるモダンなデザインを簡単に実現できる。
MuiComponent.jsx
import React from 'react';
import { TextField, Button, Grid, Box, Typography } from '@mui/material';
import SendIcon from '@mui/icons-material/Send';
function MuiComponent() {
const handleSubmit = (event) => {
event.preventDefault();
// フォーム送信の処理
};
return (
<React.Fragment>
<Box
sx={{
color: 'blue',
fontSize: '20px',
padding: '10px',
border: '1px solid black',
}}
>
Material-UI (MUI)です。
</Box>
<Box
component="form"
onSubmit={handleSubmit}
sx={{
maxWidth: 400,
margin: 'auto',
padding: 3,
border: '1px solid #ccc',
borderRadius: 2,
boxShadow: 3,
marginTop:2,
}}
>
<Typography variant="h6" align="center" gutterBottom>
以下のようなフォームも作れる
</Typography>
<Grid container spacing={2}>
<Grid item xs={12}>
<TextField
fullWidth
id="username"
label="Username"
variant="outlined"
required
/>
</Grid>
<Grid item xs={12}>
<TextField
fullWidth
id="email"
label="Email"
type="email"
variant="outlined"
required
/>
</Grid>
<Grid item xs={12}>
<TextField
fullWidth
id="password"
label="Password"
type="password"
variant="outlined"
required
/>
</Grid>
<Grid item xs={12}>
<Button
type="submit"
variant="contained"
color="primary"
fullWidth
endIcon={<SendIcon />}
>
Submit
</Button>
</Grid>
</Grid>
</Box>
</React.Fragment>
);
}
export default MuiComponent;
プレビュー
Ant Design
エンタープライズ向けのReact UIライブラリで、中国のアリババグループの関連企業であるアントグループ(Ant Group)が開発したUIフレームワーク。デザインシステムと多くのコンポーネントを提供し、プロフェッショナルなアプリケーションの構築に向いている。
AntDComponent.jsx
import React from 'react';
import { Form, Input, Button, Typography, Row, Col, Card } from 'antd';
import { SendOutlined } from '@ant-design/icons';
function AntDComponent() {
const handleSubmit = (values) => {
console.log('Form values: ', values);
// フォーム送信の処理
};
return (
<React.Fragment>
<Card
style={{
color: 'blue',
fontSize: '20px',
padding: '10px',
border: '1px solid black',
marginBottom: '16px',
}}
>
Ant Designです。
</Card>
<Card
style={{
maxWidth: 400,
margin: 'auto',
padding: '24px',
border: '1px solid #ccc',
borderRadius: '8px',
boxShadow: '0px 0px 15px rgba(0, 0, 0, 0.1)',
}}
>
<Typography.Title level={4} style={{ textAlign: 'center', marginBottom: '24px' }}>
以下のようなフォームも作れる
</Typography.Title>
<Form
layout="vertical"
onFinish={handleSubmit}
>
<Row gutter={16}>
<Col span={24}>
<Form.Item
name="username"
label="ユーザ名"
rules={[{ required: true, message: 'Please input your username!' }]}
>
<Input placeholder="ユーザ名を入れてね" />
</Form.Item>
</Col>
<Col span={24}>
<Form.Item
name="email"
label="メール"
rules={[{ required: true, message: 'Please input your email!', type: 'email' }]}
>
<Input placeholder="メールアドレスを入れてね" />
</Form.Item>
</Col>
<Col span={24}>
<Form.Item
name="password"
label="パスワード"
rules={[{ required: true, message: 'Please input your password!' }]}
>
<Input.Password placeholder="パスワードだよ" />
</Form.Item>
</Col>
<Col span={24}>
<Button
type="primary"
htmlType="送信"
block
icon={<SendOutlined />}
>
Submit
</Button>
</Col>
</Row>
</Form>
</Card>
</React.Fragment>
);
}
export default AntDComponent;
プレビュー
Chakra UI
Reacto向けのシンプルでモジュール化されたコンポーネントライブラリ。レスポンシブでテーマ化されたUIを効率的に構築できる。標準でアクセシビリティにも配慮されており、カスタマイズが簡単なコンポーネントが豊富に揃っている。
ChakraUIComponent.jsx
import React from 'react';
import { Box, Button, FormControl, FormLabel, Input, Grid, Heading } from '@chakra-ui/react';
import { ArrowForwardIcon } from '@chakra-ui/icons';
function ChakraUIComponent() {
const handleSubmit = (event) => {
event.preventDefault();
// フォーム送信の処理
};
return (
<React.Fragment>
<Box
color="blue"
margin={4}
fontSize="20px"
padding="10px"
border="1px solid black"
marginBottom="16px"
>
Chakra UIです。
</Box>
<Box
as="form"
onSubmit={handleSubmit}
maxWidth="400px"
margin="auto"
padding="24px"
border="1px solid #ccc"
borderRadius="8px"
boxShadow="lg"
marginTop="16px"
>
<Heading as="h6" size="md" textAlign="center" marginBottom="24px">
以下のようなフォームも作れる
</Heading>
<Grid templateColumns="repeat(1, 1fr)" gap={4}>
<FormControl isRequired>
<FormLabel htmlFor="username">ユーザ名</FormLabel>
<Input id="username" placeholder="ユーザ名を入れてね" />
</FormControl>
<FormControl isRequired>
<FormLabel htmlFor="email">メール</FormLabel>
<Input id="email" type="email" placeholder="メールアドレスをいれてね" />
</FormControl>
<FormControl isRequired>
<FormLabel htmlFor="password">パスワード</FormLabel>
<Input id="password" type="password" placeholder="パスワードだよ" />
</FormControl>
<Button
type="submit"
colorScheme="blue"
width="full"
rightIcon={<ArrowForwardIcon />}
>
Submit
</Button>
</Grid>
</Box>
</React.Fragment>
);
}
export default ChakraUIComponent;
Fluent UI
Microsoftが開発したReact向けのUIフレームワーク。Microsoftの製品(Office365など)で使用されている一貫したデザインシステムに基づいている。
FluentUIComponent.jsx
import React from 'react';
import { TextField, PrimaryButton, Stack, Text, Label } from '@fluentui/react';
import { initializeIcons } from '@fluentui/react/lib/Icons';
import { FontIcon } from '@fluentui/react/lib/Icon';
initializeIcons();
function FluentUIComponent() {
const handleSubmit = (event) => {
event.preventDefault();
// フォーム送信の処理
};
return (
<React.Fragment>
<Stack
tokens={{ childrenGap: 10 }}
styles={{
root: {
color: 'blue',
fontSize: '20px',
padding: '10px',
border: '1px solid black',
marginBottom: '16px',
}
}}
>
<Text>Fluent UIです。</Text>
</Stack>
<Stack
as="form"
onSubmit={handleSubmit}
tokens={{ childrenGap: 15 }}
styles={{
root: {
maxWidth: 400,
margin: 'auto',
padding: '24px',
border: '1px solid #ccc',
borderRadius: '8px',
boxShadow: '0 0 10px rgba(0, 0, 0, 0.1)',
marginTop: '16px',
}
}}
>
<Text variant="large" styles={{ root: { textAlign: 'center', marginBottom: '24px' } }}>
以下のようなフォームも作れる
</Text>
<Stack tokens={{ childrenGap: 10 }}>
<Label htmlFor="username">ユーザ名</Label>
<TextField
id="username"
placeholder="ユーザ名を入れてね"
required
styles={{ root: { width: '100%' } }}
/>
<Label htmlFor="email">メール</Label>
<TextField
id="email"
type="email"
placeholder="メールアドレスを入れてね"
required
styles={{ root: { width: '100%' } }}
/>
<Label htmlFor="password">Password</Label>
<TextField
id="password"
type="パスワード"
placeholder="パスワードだよ"
required
styles={{ root: { width: '100%' } }}
/>
<PrimaryButton
type="submit"
text="Submit"
styles={{ root: { width: '100%', marginTop: '16px' } }}
iconProps={{ iconName: 'Send' }}
/>
</Stack>
</Stack>
</React.Fragment>
);
}
export default FluentUIComponent;
プレビュー
まとめ
もしCSSが苦手でなく細かいスタイリングをしたい場合は、「Styled Components」「Emotion」あたり、とりあえずスピーディーにUIを作っていきたいという場合は「MUI」「Ant Design」などのUIフレームワーク、CSSの知識を活かしつつも迅速・柔軟にUIを作りたい場合は「Tailwind CSS」等など。つくりたいものに応じて選択していくといいと思います。(難しいんですが)
冒頭も述べましたが、結局、どの手法を採用するかは、プロジェクトの要件、メンバーのスキル、メンバーの好み、プロジェクト(アプリケーション)の規模、将来的な拡張性/保守性などによってしまうので、決定的に「これがいい」というのは無いのですね。
ちなみに、今作ろうとしているアプリケーションはMUIを採用することにしました。
Emotionもかなり良さそうだったのですが、現状作ろうとしているアプリケーションの要件も踏まえてMUIが良いかなと。
Reactを触りはじめて数ヶ月なのと、他のスタイリング方法としっかり比較できているわけではないので、後から色々問題も出てくるかもしれませんけど。
最後まで読んでいただきありがとうございました。