ReactでのStyleのあてかた

## ■はじめに

最近、Reactを学ぶ中で、スタイルの当て方が複数あったり、CSSとは書き方が違ったりと学びがあったのでまとめます。

## ■インラインスタイルをあてる方法

### ◆インラインスタイル

```jsx:App.jsx
function App() {
  return (
    <>
      <button>ボタン</button>
      <div style={{ color: 'red', fontSize: 20 }}>クリックされました</div>
    </>
  )
}

export default App
```

![スクリーンショット 2025-01-21 8.04.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307408/79b69c47-8334-5813-1387-c5935f582f98.png)

インラインで、スタイルを当てるには、HTMLタグに対して`style={}`を書きます。
`{}`がJSXという、HTMLの中にJSを書くための構文になります。
`{}`の中に更に`{}`を書きます。
２つ目の`{}`はオブジェクトを表しています。
その中にスタイルを書いていきます。
CSSを書く場合、`font-size`と書きますが、ReactではfontSizeと大文字で書きます。
数値に関しても「20」と書くと、勝手に単位を補完してくれます。

```jsx:App.jsx
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"`と見慣れた感じで書くこともできます。

### ◆スタイルをオブジェクトとして切り出す

```jsx:App.jsx
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に応じて、動的にスタイルを変える

```jsx:App.jsx
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する

```jsx:App.jsx
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;
```

```jsx:App.css
#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する問題点

```jsx:App.jsx
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;
```

```css:App.css
#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`にボタンの表示とデザインを当てています。

```jsx:SubButton.jsx
import './SubButton.css';

const SubButton = () => {
	return <button className="btn">サブボタン</button>;
};

export default SubButton;
```

```css:SubButton.css
.btn {
	background-color: #4CAF50;
}
```

`App.jsx`に表示しているボタンと違うデザインを当てたサブボタンを作りたいと思い、
`src/components/SubButton.jsx`にサブボタンコンポーネントを作りました。
`src/components/SubButton.css`でサブボタンにだけ適用したいという意図で背景色を指定しました。

![スクリーンショット 2025-01-26 12.14.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307408/8fd9311b-cfc5-dee9-d65b-e7c5dce8be2a.png)

この状態で画面をリロードすると、「ボタン」にも背景色が適用されてしまいます。

このように、外部CSSファイルをimportすると、クラス名が被って意図しない要素にもデザインが当たるという問題点があります。
デザインが増えると、競合を調査したり、競合を考慮しながら開発するのが難しくなります。

## ■CSSモジュールを使ってスタイルを当てる

### ◆外部CSSファイルの問題点おさらい

外部CSSファイルをimportして使うと、クラス名が競合して、意図しない要素のデザインが当たるという事象がありました。
サブボタンだけを緑色にしようとすると、ボタンも緑になりました。
このような競合を防ぐ方法が、CSSモジュールです。

### ◆CSSモジュールを使ってみる

```jsx:App.jsx
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
```

```css:App.module.css

.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';`の読み込んで変数に格納します。

![スクリーンショット 2025-01-26 14.27.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307408/1dc76895-1ea6-b881-0389-6f4e762c8854.png)

`style`の中身を確認すると、オブジェクトになっており、プロパティの値に一意な値がセットされています。
この値を`className={style.btn}`のように指定して、要素のクラス名として適用します。

こうすることで、コンポーネントごとに一意なクラス名が適用されるため、同一クラス名に意図しないCSSが適用されることを防ぐことができます。

```jsx:SubButton.jsx
import style from './SubButton.module.css';

const SubButton = () => {
	return <button className={style.btn}>サブボタン</button>;
};

export default SubButton;
```

```css:SubButton.module.css
.btn {
	display: inline-block;
	width: 100px;
	height: 50px;
	border: none;
	border-radius: 10px;
	margin: 5px;
	background-color: #4caf50;
}
```

サブボタンコンポーネントのCSSファイルも、CSSモジュールに書き換えてみます。

![スクリーンショット 2025-01-26 14.31.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307408/732e8067-2df2-a445-d0a2-d2eedd196241.png)


クラス名を確認すると、ボタンとサブボタンのクラス名が違う事がわかります。
違うクラス名が当たることで、デザインをコンポーネント事に分けることができています。

### ◆CSSモジュールの注意点

```css
.btn {
	display: inline-block;
	width: 100px;
	height: 50px;
	border: none;
	border-radius: 10px;
	margin: 5px;
	background-color: #4caf50;
}

button {
	background-color: red;
}
```

![スクリーンショット 2025-01-26 14.33.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307408/2c973cd7-13df-c8ef-21b9-fb83d0319048.png)

CSSモジュール内で、buttonというタグ名に直接スタイルを書くと、グローバルに適用されるCSSになるため、意図しないデザインが当たる可能性があります。

そのため、クラス名かid名にデザインを当てるようにしたほうがいいです。

## ■CSS-in-JSでスタイルを当てる

### ◆CSS-in-JSとは何か？

CSS-in-JSは、JSの中にCSSを書けるようにする技術です。
CSS-in-JSを実現するライブラリとして「Panda CSS」「styles-components」「Emotion」がなどあります。

https://panda-css.com/

https://styled-components.com/

https://emotion.sh/docs/introduction

### ◆styles-componentsをインストールする

```zsh
npm install styled-components
```
まずは、npm install styled-componentsでインストールします。

```jsx:App.jsx
import styled from "styled-components";

console.dir(styled);
```

JSXのファイル内で、`styled-components`をインストールして、変数に代入します。

今回はstyledという変数に代入しています。

![スクリーンショット 2025-01-28 8.04.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307408/e6c2ef9d-84dd-ac6f-a11b-2de3b8b61c2b.png)

ログに出力してみると、stylesの中にプロパティと値として関数が設定されている事がわかります。

プロパティ名は、HTMLタグの数だけ存在しています。

### ◆styles-componentsを使ってみる

```jsx:App.jsx
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`とすると<button>タグが生成されます。

![スクリーンショット 2025-01-28 8.16.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307408/5df065d3-17fa-3958-6c44-89aedc47fdb8.png)

実際にタグを見てみると、<button>タグが生成されています。
このタグに一意なクラス名が当てられていることも確認できます。
一意なクラス名が当てられるため、インラインCSSや外部CSSファイルを読み込むパターンと比較して、CSSの競合が起こらないというメリットがあります。

### ◆動的にスタイルを変える方法

```jsx:App.jsx
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の継承とオーバーライド

```jsx:App.jsx
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したときのスタイルを当てることもできます。

![スクリーンショット 2025-01-28 8.28.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307408/b62febdb-9e25-5100-9097-0e6716d702bf.png)

画像のようにボタンとサブボタンでクラス名が一意になるため、競合しないようになります。

## ■まとめ

デザインの衝突を避けるために、CSS-in-JSを使うのが１番にいいなと思いました