■はじめに
最近、Reactを学ぶ中で、スタイルの当て方が複数あったり、CSSとは書き方が違ったりと学びがあったのでまとめます。
■インラインスタイルをあてる方法
◆インラインスタイル
function App() {
return (
<>
<button>ボタン</button>
<div style={{ color: 'red', fontSize: 20 }}>クリックされました</div>
</>
)
}
export default App
インラインで、スタイルを当てるには、HTMLタグに対してstyle={}を書きます。
{}がJSXという、HTMLの中にJSを書くための構文になります。
{}の中に更に{}を書きます。
2つ目の{}はオブジェクトを表しています。
その中にスタイルを書いていきます。
CSSを書く場合、font-sizeと書きますが、ReactではfontSizeと大文字で書きます。
数値に関しても「20」と書くと、勝手に単位を補完してくれます。
import { useState } from "react"
const Example = () => {
const [isSelected, setIsSelected] = useState(false);
const clickHandler = () => setIsSelected(prev => !prev);
return (
<>
<button onClick={clickHandler}>ボタン</button>
<div style={{ color: "red", "font-size": "20px" }}>{isSelected && "クリックされました。"}</div>
</>
)
};
export default Example;
ちなみに"font-size": "20px"と見慣れた感じで書くこともできます。
◆スタイルをオブジェクトとして切り出す
function App() {
const style = {
width: '100px',
height: '50px',
border: 'none',
borderRadius: '10px',
backgroundColor: 'skyblue',
}
return (
<>
<button style={style}>ボタン</button>
<div style={{ color: 'red', fontSize: 20 }}>クリックされました</div>
</>
)
}
export default App
変数を宣言して、その中にstyleを書くこともできる。
◆stateに応じて、動的にスタイルを変える
import { useState } from 'react';
function App() {
const [isClick, setClick] = useState(false);
const style = {
width: '100px',
height: '50px',
border: 'none',
borderRadius: '10px',
backgroundColor: isClick ? 'pink' : 'skyblue',
};
const clickAction = () => {
setClick((prev) => !prev);
};
return (
<>
<button style={style} onClick={clickAction}>
ボタン
</button>
<div style={{ color: 'red', fontSize: 20 }}>{isClick ? 'クリックされました' : ''}</div>
</>
);
}
export default App;
isClickをstateで管理して、isClickの値に応じてスタイルを動的に変えることもできます。
◆インラインスタイルの問題点
-
::before,::after,:activeなどの疑似要素は使えない。 -
@media (min-width: 600px) {}など、メディアクエリは使えない。 - インラインスタイルで当てると、優先度が高くなり、スタイルを上書きするのが難しくなる。
- コンポーネントが再レンダリングされると、スタイルが再計算されるので、パフォーマンスが落ちる。
- ボタンを複数作って、同じスタイルを当てるときに、毎回
style={style}を書く必要がある。
■外部CSSファイルを使う方法
◆外部CSSファイルをimportする
import { useState } from 'react';
import './App.css';
function App() {
const [isClick, setClick] = useState(false);
const clickAction = () => {
setClick((prev) => !prev);
};
return (
<>
<button className='btn' onClick={clickAction}>
ボタン
</button>
<div className='text'>{isClick ? 'クリックされました' : ''}</div>
</>
);
}
export default App;
#root {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.btn {
display: inline-block;
width: 100px;
height: 50px;
border: none;
border-radius: 10px;
margin: 5px;
}
.text {
color: 'red';
font-size: '20px';
text-align: center;
}
外部CSSファイルを使うには、jsxファイル内で、import './App.css';というようにファイルをimportして使います。
CSSも通常のCSSで書くため、インラインスタイルとは違って、width: 100px;などを文字列として
width: ‘100’と定義する必要はありません。
◆外部CSSファイルをimportする問題点
import { useState } from 'react';
import './App.css';
import SubButton from './components/SubButton';
function App() {
const [isClick, setClick] = useState(false);
const clickAction = () => {
setClick((prev) => !prev);
};
return (
<>
<button className="btn" onClick={clickAction}>
ボタン
</button>
<SubButton />
<div className="text">{isClick ? 'クリックされました' : ''}</div>
</>
);
}
export default App;
#root {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.btn {
display: inline-block;
width: 100px;
height: 50px;
border: none;
border-radius: 10px;
margin: 5px;
}
.text {
color: 'red';
font-size: '20px';
}
先ほどと同じように、App.jsxとApp.cssにボタンの表示とデザインを当てています。
import './SubButton.css';
const SubButton = () => {
return <button className="btn">サブボタン</button>;
};
export default SubButton;
.btn {
background-color: #4CAF50;
}
App.jsxに表示しているボタンと違うデザインを当てたサブボタンを作りたいと思い、
src/components/SubButton.jsxにサブボタンコンポーネントを作りました。
src/components/SubButton.cssでサブボタンにだけ適用したいという意図で背景色を指定しました。
この状態で画面をリロードすると、「ボタン」にも背景色が適用されてしまいます。
このように、外部CSSファイルをimportすると、クラス名が被って意図しない要素にもデザインが当たるという問題点があります。
デザインが増えると、競合を調査したり、競合を考慮しながら開発するのが難しくなります。
■CSSモジュールを使ってスタイルを当てる
◆外部CSSファイルの問題点おさらい
外部CSSファイルをimportして使うと、クラス名が競合して、意図しない要素のデザインが当たるという事象がありました。
サブボタンだけを緑色にしようとすると、ボタンも緑になりました。
このような競合を防ぐ方法が、CSSモジュールです。
◆CSSモジュールを使ってみる
import { useState } from 'react';
import style from './App.module.css';
import SubButton from './components/SubButton';
console.log(style);
function App() {
const [isClick, setClick] = useState(false);
const clickAction = () => {
setClick((prev) => !prev);
};
return (
<>
<button className={style.btn} onClick={clickAction}>
ボタン
</button>
<SubButton />
<div className="text">{isClick ? 'クリックされました' : ''}</div>
</>
);
}
export default App;
src/App.jsx
.btn {
display: inline-block;
width: 100px;
height: 50px;
border: none;
border-radius: 10px;
margin: 5px;
}
.text {
color: 'red';
font-size: '20px';
}
ここで注目したいのはファイル名です。
拡張子が.module.cssになっています。
CSSモジュールを使う場合は、拡張子に.module.cssと書きます。
JSXファイル側では、import style from './App.module.css';の読み込んで変数に格納します。
styleの中身を確認すると、オブジェクトになっており、プロパティの値に一意な値がセットされています。
この値をclassName={style.btn}のように指定して、要素のクラス名として適用します。
こうすることで、コンポーネントごとに一意なクラス名が適用されるため、同一クラス名に意図しないCSSが適用されることを防ぐことができます。
import style from './SubButton.module.css';
const SubButton = () => {
return <button className={style.btn}>サブボタン</button>;
};
export default SubButton;
.btn {
display: inline-block;
width: 100px;
height: 50px;
border: none;
border-radius: 10px;
margin: 5px;
background-color: #4caf50;
}
サブボタンコンポーネントのCSSファイルも、CSSモジュールに書き換えてみます。
クラス名を確認すると、ボタンとサブボタンのクラス名が違う事がわかります。
違うクラス名が当たることで、デザインをコンポーネント事に分けることができています。
◆CSSモジュールの注意点
.btn {
display: inline-block;
width: 100px;
height: 50px;
border: none;
border-radius: 10px;
margin: 5px;
background-color: #4caf50;
}
button {
background-color: red;
}
CSSモジュール内で、buttonというタグ名に直接スタイルを書くと、グローバルに適用されるCSSになるため、意図しないデザインが当たる可能性があります。
そのため、クラス名かid名にデザインを当てるようにしたほうがいいです。
■CSS-in-JSでスタイルを当てる
◆CSS-in-JSとは何か?
CSS-in-JSは、JSの中にCSSを書けるようにする技術です。
CSS-in-JSを実現するライブラリとして「Panda CSS」「styles-components」「Emotion」がなどあります。
◆styled-jsxでスタイルをあてる
●styled-jsxのインストールと設定
npm install --save styled-jsx
styled-jsxをインストールします。
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
import { fileURLToPath } from 'url';
// https://vite.dev/config/
export default defineConfig({
plugins: [
react({
babel: { plugins: ['styled-jsx/babel'] },
}),
]
});
Viteの設定にbabel: { plugins: ['styled-jsx/babel'] },を追加します。
●ローカルコンポーネントのみにスタイルをあてる
export const StyledJsx = () => {
return (
<>
<div className="containerStyle">
<p className="title">Styled JSX Component</p>
<button className="buttonStyle">Styled JSX Button</button>
</div>
<style jsx>
{`
.containerStyle {
border: 2px solid purple;
border-radius: 20px;
padding: 8px;
margin: 8px;
display: flex;
justify-content: space-around;
align-items: center;
}
.title {
margin: 0;
color: purple;
}
.buttonStyle {
background-color: purple;
border: none;
border-radius: 8px;
padding: 8px 16px;
cursor: pointer;
font-weight: bold;
color: white;
transition: background-color 0.3s ease;
}
.buttonStyle:hover {
background-color: darkviolet;
}
`}
</style>
</>
);
};
export const Sample = () => {
return (
<div className="containerStyle">
<p className="title">Sample JSX Component</p>
<button className="buttonStyle">Sample JSX Button</button>
</div>
);
};
import './App.css';
import { StyledJsx } from './components/StyledJsx';
import { Sample } from './components/Sample';
function App() {
return (
<>
<div className="App">
<Sample />
<StyledJsx />
</div>
</>
);
}
export default App;
styled-jsxは<style jsx></style>の中にCSSを書きます。
プロパティ名は通常のCSSのようにborder-radiusと書きます。
.buttonStyle:hoverなどの疑似要素もかけます。
<Sample />コンポーネントと<StyledJsx />コンポーネントのクラス名が同じですが、<style jsx>と書くことでコンポーネントのクラス名にjsx-175408301のような文字が追加されて、スタイルを書いたコンポーネントのみにCSSが適用されます。
もし全てのコンポーネントに適用したいスタイルの場合は、<style jsx global></style>とglobalを追加することで、コンポーネントが異なっていても同じクラス名がついているタグにデザインが適用されます。
◆styles-componentsでスタイルをあてる
●styles-componentsをインストールする
npm install styled-components
まずは、npm install styled-componentsでインストールします。
import styled from "styled-components";
console.dir(styled);
JSXのファイル内で、styled-componentsをインストールして、変数に代入します。
今回はstyledという変数に代入しています。
ログに出力してみると、stylesの中にプロパティと値として関数が設定されている事がわかります。
プロパティ名は、HTMLタグの数だけ存在しています。
●styles-componentsを使ってみる
import { useState } from 'react';
import styled from 'styled-components';
const ButtonStyle = styled.button`
display: inline-block;
width: 100px;
height: 50px;
border: none;
border-radius: 10px;
margin: 5px;
background-color: skyblue;
`;
const TextStyle = styled.div`
color: red;
font-size: 20px;
`;
function App() {
const [isClick, setClick] = useState(false);
const clickAction = () => {
setClick((prev) => !prev);
};
return (
<>
<ButtonStyle onClick={clickAction}>ボタン</ButtonStyle>
<TextStyle>{isClick ? 'クリックされました' : 'クリックされていません'}</TextStyle>
</>
);
}
export default App;
ButtonStyleやTextStyleがstyled-componentsを使ったスタイルのあて方です。
styled.buttonとするとタグが生成されます。
実際にタグを見てみると、タグが生成されています。
このタグに一意なクラス名が当てられていることも確認できます。
一意なクラス名が当てられるため、インラインCSSや外部CSSファイルを読み込むパターンと比較して、CSSの競合が起こらないというメリットがあります。
●動的にスタイルを変える方法
import { useState } from 'react';
import styled from 'styled-components';
const ButtonStyle = styled.button`
display: inline-block;
width: 100px;
height: 50px;
border: none;
border-radius: 10px;
margin: 5px;
background-color: ${(props) => (props.$isClick ? 'skyblue' : 'green')};
`;
const TextStyle = styled.div`
color: red;
font-size: 20px;
`;
function App() {
const [isClick, setClick] = useState(false);
const clickAction = () => {
setClick((prev) => !prev);
};
return (
<>
<ButtonStyle $isClick={isClick} onClick={clickAction}>
ボタン
</ButtonStyle>
<TextStyle>{isClick ? 'クリックされました' : 'クリックされていません'}</TextStyle>
</>
);
}
export default App;
$isClick={isClick}でstateで管理しているisClickをButtonStyleに渡しています。
ButtonStyleでは、propsとして受け取れるため、props.$isClickでアクセスできます。
isClickの値の応じて、スタイルを動的に変えることができます。
●styleの継承とオーバーライド
import { useState } from 'react';
import styled from 'styled-components';
const ButtonStyle = styled.button`
display: inline-block;
width: 100px;
height: 50px;
border: none;
border-radius: 10px;
margin: 5px;
background-color: ${(props) => (props.$isClick ? 'skyblue' : 'green')};
`;
const TextStyle = styled.div`
color: red;
font-size: 20px;
`;
const SubButton = styled(ButtonStyle)`
background-color: orange;
&:hover {
background-color: red;
}
`;
function App() {
const [isClick, setClick] = useState(false);
const clickAction = () => {
setClick((prev) => !prev);
};
return (
<>
<ButtonStyle $isClick={isClick} onClick={clickAction}>
ボタン
</ButtonStyle>
<SubButton>サブボタン</SubButton>
<TextStyle>{isClick ? 'クリックされました' : 'クリックされていません'}</TextStyle>
</>
);
}
export default App;
styled(ButtonStyle)とすることでButtonStyleのスタイルを継承できます。
また、その中でbackground-color: orange;と書くことでスタイルをオーバーライドできます。
さらに、&:hoverとすることで、hoverしたときのスタイルを当てることもできます。
画像のようにボタンとサブボタンでクラス名が一意になるため、競合しないようになります。
■まとめ
デザインの衝突を避けるために、CSS-in-JSを使うのが1番にいいなと思いました








