【React】なるべく学習コストをかけずにMaterial UIをカスタマイズしたい

みなさんこんにちは。

デザインを作ったり、HTMLを書いたり、CSSを書いたり、Reactを書いたり、
graphQLのクエリを考えたりしているちょっとハイブリッドな人です。

今回はReactプロジェクトでMaterial UIを使用した場合に、なるべく学習コストをかけずに
スタイルをカスタマイズすることを考えてみました。

Material UIはカスタマイズ項目はかなりたくさんあり、
指定方法を把握するのがしんどかったので...

## この記事の対象者
- 基本的なHTML/CSSの知識があり、Reactにも興味がある人
- ReactプロジェクトでMaterial UIを使っていてカスタムスタイルを当てたい人
- ReactプロジェクトでMaterial UIを部分的に使いたいけどスタイルの当て方は統一したい人
- React + Material UIプロジェクトのスタイル実装に駆り出された、普段はHTML+CSSでコーディングしている人
- Reactプロジェクトのスタイル実装をどうするか迷っている人

## この記事があまり参考にならならいかもしれない人
- React使ってない人
- Material UI めっちゃわかる人
- Material UIでスタイルを統一しているプロジェクトに関わっている人

## Material UIとは
Reactのコンポーネントライブラリ(version5からMUIが正式名称になっているようです)
[Material UI](https://mui.com/) (以降、MUIとします)

### MUIのメリット
- デフォルトスタイルのあたったコンポーネントを組み合わせるだけである程度画面を作れる
- バックエンドエンジニアが表示確認する際にスタイルが実装されていなくても、ある程度確認できる
- レスポンシブデザインにも対応している
- Reactの設計方法としても取り入れられる[atomic-design](https://atomicdesign.bradfrost.com/)と相性が良い

以下はMUIでテーブルを作成した例です。
何もスタイルを当てていないですが、ある程度見れるものができあがります。
```react:SampleTable.tsx
import {
    Button,
    Table,
    TableBody,
    TableCell,
    TableContainer,
    TableHead,
    TableRow,
    TextField,
} from '@mui/material';

export const SampleTable = (): JSX.Element => {
    return (
        <>
            <TableContainer>
                <Table>
                    <TableHead>
                        <TableRow>
                            <TableCell>ID</TableCell>
                            <TableCell>名前</TableCell>
                            <TableCell>入力</TableCell>
                        </TableRow>
                    </TableHead>
                    <TableBody>
                        <TableRow>
                            <TableCell>1</TableCell>
                            <TableCell>山田太郎</TableCell>
                            <TableCell>
                            <TextField
                                type="text"
                                value=""
                                required
                                label="入力"
                            />
                            </TableCell>
                        </TableRow>
                        <TableRow>
                            <TableCell>2</TableCell>
                            <TableCell>山田花子</TableCell>
                            <TableCell>
                            <TextField
                                type="text"
                                value=""
                                required
                                label="入力"
                            />
                            </TableCell>
                        </TableRow>
                    </TableBody>
                </Table>
            </TableContainer>
            <Button>もっとみる</Button>
        </>
    );
};
```
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2881616/35d23daa-5fab-3e4f-bdf5-9da3007ecc41.png)

### MUIのデメリット
- 細かいスタイル調整は上書きで行う必要がある
- 上書きスタイルの指定方法が特殊なので学習コストがかかる
- オブジェクトの形式で記述するので通常のCSSと命名規則やプロパティの指定方法が異なりコード補完が効かない
- オブジェクト形式なので、figmaやxdで吐き出したCSSのコードをそのまま使えない
- 単位が特殊(単位はテーマのspacingに設定したピクセルとなりデフォルトは8px)

以下はメリットの部分に書いたコードにスタイルを上書きして整えた例です。
MUI version5の場合オブジェクトの形式でスタイルを記述してsx Propに指定します。
```react:SampleTable.tsx
import {
    Box,
    Button,
    Table,
    TableBody,
    TableCell,
    TableContainer,
    TableHead,
    TableRow,
    TextField,
} from '@mui/material';

const box_style = {
    display: 'flex',
    alignItems: 'center', /* キャメルケース　&　カンマ区切りで辛い */
    flexDirection: 'column',
    m: '0 auto', /* m = margin 省略しすぎて初見さんお断り ml = margin-leftとかも*/
}

export const SampleTable = (): JSX.Element => {
    return (
        <Box sx={box_style}>
            <TableContainer
                sx={{
                    width: 500,
                    m: '16px auto', /* 文字を含む場合は''で囲む必要がある */
                    p: 4,           /* p = padding デフォルト単位は8pxなのでこの場合だと(4 * 8px = 32px) */
                    backgroundColor: '#fafafa',
                    border: '1px solid #ddd',
                }}
            >
                <Table>
                    <TableHead>
                        <TableRow>
                            <TableCell>ID</TableCell>
                            <TableCell>名前</TableCell>
                            <TableCell>入力</TableCell>
                        </TableRow>
                    </TableHead>
                    <TableBody>
                        <TableRow>
                            <TableCell>1</TableCell>
                            <TableCell>山田太郎</TableCell>
                            <TableCell>
                            <TextField
                                type="text"
                                value=""
                                required
                                label="入力"
                            />
                            </TableCell>
                        </TableRow>
                        <TableRow>
                            <TableCell>2</TableCell>
                            <TableCell>山田花子</TableCell>
                            <TableCell>
                            <TextField
                                type="text"
                                value=""
                                required
                                label="入力"
                            />
                            </TableCell>
                        </TableRow>
                    </TableBody>
                </Table>
            </TableContainer>
　　　　　　　　　　　　　　　　　　　　　　　　　{/* コンポーネントへの指定でカラーや形を変更できるが、既存のスタイル指定と統一性がない */}
            <Button variant="contained" color="success">もっと見る</Button>
        </Box>
    );
};
```
![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2881616/51de87e8-a298-79ff-06ad-135c24f6977d.png)

上記ではBoxコンポーネントにflexを指定して普通のCSSを書く感覚でレイアウトを整えています。
MUIにはGridなどレイアウトに関するコンポーネントもあるのですが、
今回は学習コストを下げることと、スタイル指定方法を統一することが主なので触れません。


ちなみにMUI version4の場合(以下)はスタイル指定がさらに辛い。
```react:SampleTable.tsx
import {
    Button,
    ...
} from '@mui/material';
import { makeStyles, createStyles } from '@material-ui/core/styles';

// フックを作成
const useStyles = makeStyles((theme) => createStyles({
    box: {
        display: 'flex',
        alignItems: 'center',
        flexDirection: 'column',
        m: '0 auto',
    },
}));

export const SampleTable = (): JSX.Element => {

    // フックで className を取得
    const classes = useStyles();

    return (
        <Box className={classes.box}>
            <TableContainer>
            ...
            </TableContainer>
            <Button variant="contained" color="green">もっと見る</Button>
        </Box>
    );
};
```

これはどげんかせんといかん。

## デメリットをなんとかする

### やりたいこと
- 記法(プロパティの指定方法など)を通常のCSS/SASS/SCSSと同じにしたい
- コード補完機能を使いたい

### デメリットが解消したら...!
- 記法を今までと合わせることで、学習コストがかからないし既存スタイルの移植もしやすい。
- 基本的なスタイルはMUIを使うので１から作る必要がなくなり、必要な部分だけ既存の記法でカスタマイズできるようになる。
- エンジニアが簡易的に見た目を作って動作確認する場合もデフォルトスタイルがあたっているので安心。
 (全てを無に帰す系のreset.cssだとブラウザデフォルトのスタイルが消えるので、フォーム系が表示されていないように見えて確認が辛い。)

### Emotionを使ってデメリットを解消する

#### Emotionとは
[Emotion](https://Emotion.sh/docs/introduction)
- CSS-in-JSのライブラリ(JSファイルにCSSを書くことができる)
- 代表的なCSS-in-JSであるstyled-componentsよりも後発で痒いところに手が届く


Emotionの詳細な使用方法には今回触れませんが、簡単にメリット、デメリットを書いておきます。

#### Emotionのメリット
- スコープができるのでスタイル変数の命名が被りにくくなる。(従来で言うclass名が被る問題の解消)
- JSの変数をそのままスタイルに使える
- SASS/SCSSの記法が使える
- mixinみたいなこともできるのでブレイクポイントの設定が楽
- スタイルがタグ名に紐づかずタグ名が変わらない(styled-componentsだとタグ名にスタイルが紐づく)
- vscodeの場合、[プラグイン](https://github.com/styled-components/vscode-styled-components)の導入で補完機能も使える

#### Emotionのデメリット
- エディタによっては補完が効かない
- クラス名を使わないので多少違和感あるかも
- Nextjsを使用している場合は導入時の設定が少し面倒　([参考記事](https://zenn.dev/iwakin999/articles/7a5e11e62ba668))

#### Emotion + MUIで書き換えてみた
```react
import { css } from '@emotion/react'; // ← Emotionが使えるようにこれを追加
import {
    Box,
    Button,
    Table,
    TableBody,
    TableCell,
    TableContainer,
    TableHead,
    TableRow,
    TextField,
} from '@mui/material';

// スタイルの定義(プラグインなしだと文字扱いなので補完が効かず色分けもされない)
const box_style = css`
    display: flex;
    align-items: center;
    flex-direction: column;
    margin: 0 auto;
`;

const button_style = css`
    background-color: #2e7d32;
    color: #fff;

    :hover {
        color: #2e7d32;
        background-color: #E6FBE7;
    }
`;

export const SampleTable = (): JSX.Element => {
    return (
        <Box css={box_style}>
            {/* タグ内で指定できるが、エディタによってはこれ以下のタグの色が変わってしまうので外で定義した方が良い */}
            <TableContainer
                css={css`
                    width: 500px;
                    margin: 16px auto;
                    padding: 32px;
                    background-color: #fafafa;
                    border: 1px solid #ddd;
                `}
            >
                <Table>
                    <TableHead>
                        <TableRow>
                            <TableCell>ID</TableCell>
                            <TableCell>名前</TableCell>
                            <TableCell>入力</TableCell>
                        </TableRow>
                    </TableHead>
                    <TableBody>
                        <TableRow>
                            <TableCell>1</TableCell>
                            <TableCell>山田太郎</TableCell>
                            <TableCell>
                            <TextField
                                type="text"
                                value=""
                                required
                                label="入力"
                            />
                            </TableCell>
                        </TableRow>
                        <TableRow>
                            <TableCell>2</TableCell>
                            <TableCell>山田花子</TableCell>
                            <TableCell>
                            <TextField
                                type="text"
                                value=""
                                required
                                label="入力"
                            />
                            </TableCell>
                        </TableRow>
                    </TableBody>
                </Table>
            </TableContainer>
            <Button css={button_style}>もっと見る</Button>
        </Box>
    );
};
```
![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2881616/60b0443d-134d-f09b-a6ae-e54265df8d18.png)


これまでのclassにスタイルを当てる感覚で記述方法も同じなので
初見でも理解しやすくなったのではないでしょうか。

上記の例ではMUIコンポーネントに対してスタイルを当てていますが、
もちろんHTMLタグにも同じように当てることができます。

```react
<div css={box_style}>...</div>
```

## おまけ：コンポーネント思考のCSSについてちょっとだけ考える

CSS-in-JSはコンポーネント内にスタイルを書いていきます。
スタイルを使い回すのではなく、「スタイル込みのコンポーネント」を
再利用するという思想になっています。

これが完璧にできれば理想なのですが、現実ではなかなかうまくいきません。

例えばテーブルのコンポーネントを自作する場合。
１列目の色はこれ、２列目の幅は最大60pxにしたいなど細かいカスタマイズが必要になった場合には
どうしても上書きスタイルが発生してしまいます。

完璧に細部の調整ができる汎用テーブルコンポーネントがあれば理想ですが、
それは現実的ではないと思いますので、こういう場合は共通のテーブルスタイルをベースとして
読み込み、個々で上書きスタイルを追加する方が楽だと思います。

![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2881616/359aa7f3-3e64-b112-ff2b-34f6e80af3de.png)

CSS-in-JSでも共通スタイルを別ファイルに定義してimportしたり、
別スタイルに継承することは可能なのでその点は安心です。

## まとめ

### デザイナー(フロントコーダー)目線
- MUIを使うとラジオボタンやチェックボックスなど基本スタイルを１から書く必要がないので楽
- MUIとEmotionと一緒に使うとこれまでの記法で上書きできるので学習コストが低
- 普通のHTMLタグとMUIコンポーネントへのスタイル指定方法が同じなので迷わない

### バックエンドエンジニア目線
- CSSなしでもMUIコンポーネントを使うだけである程度の見た目になるので確認しやすい。

みなさま良い開発ライフを。