【React / Next.js】className の基本と応用

## :large_blue_diamond: 基本形

```React:例）単体のクラス
<div className="red">りんご</div>
```
```React:例）複数のクラス
<div className="blue yellow red">信号機</div>
```
通常のCSSファイルの読み込み
```css:styles.css
.red {
  color: red;
}
```
```jsx:Apple.js
import './styles.css';  // 通常のCSSファイルを読み込む

const Apple = () => {
  return <div className="red">りんご</div>;
};

export default Apple;
// <div class="red">りんご</div>  HTMLに反映されてスタイルが適用
```

## :large_blue_diamond: `{ }` の扱いについて
JSX内で`{ }` を使った場合、以下はエラーになる
```React:エラーになる例
<div className={red}>りんご</div>  // red が未定義（undefined）ならエラーになる　
```
:::note warn
<font color="red">**{ } の中身を定義せずに JSX 内で使うとエラーになる**</font>
:::

::: note info
<font color="red"> **{ } の中身はJavaScriptの式として評価される**</font>
→ 変数や関数の値として解釈される
:::

```react:OKな例
const red = "red"; // 変数red を 文字列 "red" で定義
<div className={red}>りんご</div>  // <div class="red">りんご</div>
```


以下、3つはOK
```React:OKな例
<div className={'foo'}>シングルクォート</div> // OK
<div className={"foo"}>ダブルクォート</div> // OK
<div className={`foo`}>バッククォート</div> // OK
```

:::note info
<font color="red">**バッククォート(`)で囲まれた文字列はテンプレートリテラルとして扱われ
${ } 構文で変数や式の結果を埋め込める。**</font>
:::

```react:例）テンプレートリテラルでクラス名を動的に結合
const className = "foo";
<div className={`${className} bar`}>OK</div>  // <div class="foo bar">OK</div>
```
## :large_blue_diamond: CSS ModulesとclassName
### ハードコーディングは適用されない？
```css:styles.module.css
.red {
  color: red;
}
```
```jsx:Apple.js
import styles from './styles.module.css'; //CSS Modules形式

const Apple = () => {
  return (
    <div className="red">りんご</div>  // ハードコーディングはスタイルが適用されない
  );
};

export default Apple;
```
:::note warn
<font color="red">**CSS Modulesの場合**</font>
`className="red"` のような<font color="red">**ハードコーディングはスタイルが適用されないので注意**</font>
:::

```jsx:Apple.js 修正後
import styles from './styles.module.css';

const Apple = () => {
  return (
    <div className={styles.red}>りんご</div>  // CSS Modulesではオブジェクト経由で指定
  );
};

export default Apple;
```
CSS Modulesの場合、ビルド時にハッシュ値を含むユニークな名前に変換されるため、`className="red"` のようなハードコーディングだと一致しなくなる。

![buildclass.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/769195/6286f20a-62e1-4ffa-8d39-fa1d683978aa.png)
参考１）添付は本番ビルド
`npm run dev（開発環境）`、`npm run build（本番ビルド）`といったビルド時の設定やファイル内容の変更で命名表記が変わる場合がある

参考２）`next.config.js`で必要に応じて localIdentName を変更することで、クラス名の命名形式をカスタマイズできる

:::note info
<font color="red">**CSS Modulesの特徴**</font>
- **ローカルスコープ**：
コンポーネントごとにスタイルをスコープ化し、他のコンポーネントとスタイルが競合するのを防ぐ
- **ユニークなクラス名**：
ビルド時にクラス名がハッシュ値を含んだ一意に変換され競合を防ぐ
- **Next.jsサポート**: 
Next.jsではデフォルトでCSS Modulesがサポートされており、`.module.css` 拡張子のファイルを使用することで自動的に認識される
:::

### ハイフンを含むクラス名はどうする？
```css:styles.module.css
.red-text {
  color: red;
}
```
```jsx:Apple.js
import styles from './styles.module.css';

const Apple = () => {
  return (
    <div className={styles.red-text}>りんご</div>  // ❌ エラーになる
  );
};

export default Apple;
```
:::note info
**CSS Modulesでハイフンを含むクラス名を指定する場合**
<font color="red">**ブラケット記法（styles['red-text']）でオブジェクトのキーを文字列で指定する。**
:::

```jsx:Apple.js 修正後
import styles from './styles.module.css';

const Apple = () => {
  return (
    <div className={styles['red-text']}>りんご</div>
    {/* ハイフンがある場合は文字列キーでアクセス */}
  );
};

export default Apple;
```


## :large_blue_diamond: 三項演算子で動的なclassName
条件に応じて、クラス名を動的に変更したい場合、三項演算子を使う。
```react:例）三項演算子でクラス名を動的に変更
<div className={isRed ? 'red' : 'green'}>りんご</div>
// isRed が true なら、<div class="red">りんご</div>
// isRed が false なら、<div class="green">りんご</div>
```
## :large_blue_diamond: 短絡評価（&&）とclassName

:::note
<font color="red">**短絡評価（&&）は、左辺が truthy の時に右辺を評価して返す**</font>
:::
```jsx:例）短絡評価（&&）
const result1 = true && 'red';   // "red"
const result2 = false && 'red';  // false
const result3 = 'blue' && 'red'; // "red" → 非空文字列は truthy
```
```jsx:例）classNameに短絡評価（&&）を使った場合
<div className={isRed && 'red'}>りんご</div>
```
:black_medium_small_square:isRed === true の場合

```jsx:
<div class="red">りんご</div>
```
:black_medium_small_square:isRed === false の場合
```jsx:
<div>りんご</div>  //クラス属性が表示されない
```
:::note warn
短絡評価（&&）では false, null, undefined はレンダー時に無視されるため、
属性が表示されない。
:::

### :warning: **classNameに短絡評価（&&）の左辺に数値を置かない**
```jsx:例）短絡評価（&&）
const result4 = 0 && 'item';  // 0 (0はfalthyな値)
const result5 = 1 && 'item';  // "item" (1はtruthyなので右辺が返る)
```
短絡評価（&&）の左辺に数値が入るような時
- `0`の場合 → その式全体が`0`という値に評価されるため、意図せず`0`が表示されてしまう
- `0`以外の数値の場合 → 常に左辺が truthy扱いで表示が固定される
```js:例）短絡評価（&&）の左辺に数値がある場合
<div className={itemCount && 'item'}>りんごの個数</div>
// 短絡評価（&&）の左辺が 0 の場合 <div class="0">りんごの個数</div>
// 短絡評価（&&）の左辺が 0 以外の数値の場合 <div class="item">りんごの個数</div>
```

:::note warn
短絡評価（&&）では false, null, undefined はレンダー時に無視されるため、
属性が表示されないが、0は実際そのものが表示されてしまう
:::
![classname_zero.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/769195/7a021611-1ce3-434a-8543-ac12aadf876c.png)

:black_medium_small_square:解決方法①：itemCount が 0 でない場合のみクラスを適用
```js
<div className={itemCount ? 'item' : ''}>りんごの個数</div>
```
:black_medium_small_square:解決方法②：&& の前に itemCount !== 0 （もしくは、itemCount > 0 ）を追加
```js
<div className={itemCount !== 0 && 'item'}>りんごの個数</div>
// もしくは、<div className={itemCount > 0 && 'item'}>りんごの個数</div>
```
classNameで短絡評価（&&）を使うと意図しない結果になる場合があるため、使用は避ける方がいいかもしれません :innocent:

## :large_blue_diamond: テンプレートリテラルとclassName

:::note info
<font color="red">**テンプレートリテラルの中で `${変数}` を使う事で、変数の値を文字列に埋め込める**</font>
:::
:black_medium_small_square:クラス名の一部に付与する
```react:例）クラス名の一部に付与
const PrimaryBtn = () => {
  const color = 'primary'
  return <button className={`btn btn-${color}`}>Primary</button>
}
// <button class="btn btn-primary">Primary</button>
```
:black_medium_small_square:要素に複数のクラス名を付与したい場合
```react:例）要素に複数のクラス名を付与
const ColoredButton = ({ isActive }) => {
  const bgColor = isActive ? "blue" : "gray";  // クラス名を動的に変更
  const textColor = "white" // クラス名を再利用
  
  return (
    <button className={`${bgColor} ${textColor}`}>Colored Button</button>
  )
}
// isActive が true なら <button class="blue white">Colored Button</button>
// isActive が false なら <button class="gray white">Colored Button</button>
```
**補足１）`join(' ')` を使っても同様の結果になる**
```react:例）要素に複数のクラス名を付与（join(' ')を使うパターン）
const ColoredButton = ({ isActive }) => {
  const bgColor = isActive ? "blue" : "gray";
  const textColor = "white"
  
  return (
    <button className={[bgColor, textColor].join(' ')}>Button</button>
  )
}
// isActive が true なら <button class="blue white">Button</button>
// isActive が false なら <button class="gray white">Button</button>
```
**補足２）クラスが増えると可読性が悪くなるため、配列の変数に定義を分けて組み立てる**
```react:例）可読性を意識した場合
const ColoredButton = ({ isActive, isLarge }) => {
  const classes = [
    isActive ? 'bg-blue-500' : 'bg-gray-300', // 三項演算子で動的に色を決定
    isLarge ? 'text-xl' : 'text-sm', // 三項演算子で動的にサイズを決定
    'rounded',    // 固定
    'p-4'   // 固定
  ];

  return (
    <button className={classes.join(' ')}>Button</button>
  );
};
```

:black_medium_small_square:固定クラス＋条件付きクラス
```react:例）固定クラス＋条件付きクラス
<div className={`blue ${isBoolean ? 'yellow red' : ''}`}>信号機</div>
// trueなら、<div class="blue yellow red">信号機</div>
// falseなら、<div class="blue ">信号機</div>
```
falseの場合、開発者ツール上では、以下の画像の様に

![classname_whitespace.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/769195/602e540f-558f-4c98-aa4c-3ee6841d00b3.png)

 `<div class="blue ">` となり、`blue`の後に空白ができる。

:::note warn
<font color="red">**テンプレートリテラルでは、空白や改行はそのまま生かされてしまう**</font>
:::

しかし、ブラウザが「クラス名は blue のみ」と判断するので、**特に気にしなくてよい**。

:::note info
<font color="red">**開発者ツールで末尾に空白が残るが、HTMLの仕様上、空白は無視される。**</font>
→ `class="blue  "` も `class="blue"` と同じクラスとして扱われる
:::

参考）document.querySelector('.blue ')として、`'.blue '` と書いた場合でも、ブラウザは`'.blue'` として扱う。
```
const el = document.querySelector('.blue ');
console.log(el.className);  // "blue"
```

↓　もし、気になり空白部分を削除したいなら

:black_medium_small_square:解決方法①：空白部分も考慮して組み込む
```react:例）空白部分も考慮して組み込む
<div className={`blue${isBoolean ? ' yellow red' : ''}`}>信号機</div>
```
`blue ${isBoolean` → `blue${isBoolean` 
`'yellow red'` → `' yellow red'` 
として、空白部分を考慮する。

:black_medium_small_square:解決方法②：trim()で両端の余白を削除
```react:例）trim()で余白を削除
<div className={`blue ${isBoolean ? 'yellow red' : ''}`.trim()}>信号機</div>
```
テンプレートリテラルで処理した後に trim() を適用する。
`String.prototype.trim() → 両端からホワイトスペースを取り除く`

![classname_no_whitespace_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/769195/a60603f8-5176-4598-99f1-6b84bd66c7ac.png)


## :large_blue_diamond: onClickでクラス名の切り替え

onClick イベントで className を切り替える方法。useState で状態管理し、その状態に基づいてクラス名を動的に変更する。

```react:例）App.js
import React, { useState } from "react";
import "./styles.css";

const App = () => {
  const [active, setActive] = useState(false);

  const classToggle = () => {
    setActive(!active) // 切り替え
  }

  return (
    <div>
      <h1 className={active ? "red" : ""}>りんご</h1>
      <button onClick={classToggle}>切り替え</button>
    </div>
  );
}

export default App;
```
```css:例）styles.css
.red {
  color: red;
}
```
1. 初期状態では `active` は false なので、h1 要素にはクラス名が付与されない
→ `<h1>りんご</h1>`
1. ボタンをクリックすると、classToggle 関数が実行され、
 `active ? "red" : ""` は、active が true になる → `<h1 class="red">りんご</h1>`
1. 再度、クリックすると、`active`は再び false に戻りクラスが付与されない状態に戻る
→ `<h1>りんご</h1>`

## :large_blue_diamond: className のリスク管理
className に `falsyな値（false, 0, "", null, undefined, NaN など）` を直接渡すと、HTMLのクラス属性に空白が含まれたり、不要なクラスが適用されて予期せぬ結果を引き起こす可能性がある。これを回避するはに、<font color="red">**filter メソッドを使用する事で falsy な値を除外し対応できる。**</font>

:black_medium_small_square:filter メソッドの基本的な使い方
filter メソッドは、配列を元に新しい配列を作成するメソッドで、コールバック関数の戻り値が true の要素だけが新しい配列に含まれる。

```js:例）偶数のみ抽出
const arr = [1, 2, 3, 4]
const newArr = arr.filter((v) => v % 2 === 0) 

console.log(newArr);    // [2, 4]
```
#### ① filter(v=>v)
filter メソッドのコールバック関数で true の要素を返す
```js:例）filter(v=>v)
const randomArr = [1, undefined, false, 4, null]

const newRandomArr = randomArr.filter(v => v)
// null, undefined, false は true の要素ではないため新しい配列には含まれない
console.log(newRandomArr) // [1, 4]
```
#### ② filter(Boolean)
filter メソッドに Boolean 関数をコールバック関数として渡すことで、配列から falty な値を除外する。

:::note info
<font color="red">**filter(Boolean)**</font>
Boolean 関数が、falsyな値（`false`, `0`, `""`, `null`, `undefined`, `NaN` など）をすべて false に変換するため除外できる
:::

```js:例）filter(Boolean)
const randomArr = [1, undefined, false, 4, null]
const newRandomArr = randomArr.filter(Boolean)
console.log(newRandomArr) // [1, 4]
```
filter メソッドは、配列から falsy な値を除外できるため、className に使う際に便利。
className に使用する時は ①、② どちらでも同じ結果になる。
```jsx:例）classNameに使用する
const App = ({isBoolean}) => {
  const arr = ["red", isBoolean && "big"]
  return (
    <div className={arr.filter(v => v).join(' ')}>りんご</div>
    // もしくは、<div className={arr.filter(Boolean).join(' ')}>りんご</div>
  );
};
// isBoolean が trueの場合、<div class="red big">りんご</div>
// isBoolean が falseの場合、<div class="red">りんご</div>
```
---
`配列.filter(Boolean)` は `配列.filter(v=>Boolean(v))`と同じ結果でもある。
```js
// function
const newArray = array.filter(function(v) {
  return Boolean(v);
});

// アロー関数（returnあり）
const newArray = array.filter((v) => { 
  return Boolean(v);
});

// アロー関数（省略形）
const newArray = array.filter((v) => Boolean(v));

// 関数参照（最短形）
const newArray = array.filter(Boolean);
```
```js:例）配列.filter(Boolean) は 配列.filter(v=>Boolean(v))と同じ結果
const randomArr = [1, undefined, false, 4, null];

const newRandomArr1 = randomArr.filter(Boolean);         // [1, 4]
const newRandomArr2 = randomArr.filter(v => Boolean(v)); // [1, 4]
```
## 参考
https://jsnotice.com/posts/2020-06-04/

https://ralacode.com/blog/post/react-toggle-classname/

https://1091.site/posts/nextjs/howto-add-multiple-classnames-to-nextjs-element

https://nishinatoshiharu.com/falsy-filter-method/

https://developer.mozilla.org/ja/docs/Glossary/Falsy

https://chaika.hatenablog.com/entry/2019/05/06/160031

https://kde.hateblo.jp/entry/2018/11/04/185252

https://developer.mozilla.org/ja/docs/Web/API/Document_Object_Model/Whitespace

https://webliker.info/html/03847/

https://dev.classmethod.jp/articles/tips-nextjs-with-hyphen-class-name/

https://1091.site/posts/nextjs/howto-add-multiple-classnames-to-nextjs-element

https://michaeluloth.com/javascript-filter-boolean/

https://ja.react.dev/learn/conditional-rendering

https://github.com/css-modules/css-modules

https://nextjsjp.org/docs/app/building-your-application/styling/css#css-modules