MUI (Material-UI) v5 のスタイリング方法

2021年9月16日に [MUI (Material-UI) v5 が正式にリリースされました](https://mui.com/blog/mui-core-v5/ "Introducing MUI Core v5.0")。[Material-UI v4 のリリース](https://mui.com/blog/material-ui-v4-is-out/)が 2019年3月なので、約2年半ぶりのメジャーアップデートです。[正式名称が Material-UI から MUI に変更になり](https://mui.com/blog/material-ui-is-now-mui/ "Material-UI is now MUI!")、パッケージ名も `@material-ui/*` から `@mui/*` になりました。

MUI v5 ではコンポーネントのスタイリングの実装方法や記法が一新し、Material-UI v4 以前までの独特な記法から、[Theme UI]、[chakra] などで用いられている `sx` Prop によるスタイリングになりました。
大幅な変更になるので、v4 からの移行コストは大きいですが、新たに MUI を学ぶ人には優しくなった印象です。

MUI v5 のスタイリング方法を v4 の記法と比較しながら紹介していきます。

```shell
yarn add @mui/material @emotion/react @emotion/styled
```

Introducing MUI Core v5.0
<https://mui.com/blog/mui-core-v5/>

## スタイリングの実装方法

MUI v5 では、スタイリングのコアライブラリが JSS から emotion に変わりました。この記事では説明しないので、詳しく知りたい方は [Introducing MUI Core v5.0](https://mui.com/blog/mui-core-v5/#migration-from-jss-to-emotion) を参照してください。

さて、v4 では `useStyles` という自作フックでコンポーネントのスタイリングをしていましたが、この変更によって v5 からはコンポーネントに `sx` Prop で直接スタイルを書くことができます。

## v4 と v5 の記法の違い

### Material-UI v4 の記法

`makeStyles` でスタイル用のフックを作成し、コンポーネント内で className を取得します。

```tsx
import { makeStyles, createStyles } from '@material-ui/core/styles';

// フックを作成
const useStyles = makeStyles((theme) => createStyles({
  root: {
    color: theme.palette.primary.main,
    padding: theme.spacing(2),
  },
}));

function Hoge() {
  // フックで className を取得
  const classes = useStyles();
  return (
    <div className={classes.root}>
      Hoge
    </div>
  );
}
```

### MUI v5 の記法

`sx` Prop にスタイルを直接記入します。

```tsx
import Box from '@mui/material/Box';

function Hoge() {
  return (
    <Box sx={{ color: 'primary.main', p: 2 }}>
      Hoge
    </Box>
  );
}
```

`sx` Prop によって記述量がとても少なくなり、初心者にもわかりやすい記法になりました。
color に指定した `primary.main` は、MUI テーマのパレットを利用する方法です。[v5 で新たに導入されたこの記法](https://mui.com/system/palette/)では、`theme` を引数に取る関数（後述）を指定しなくても MUI テーマのパレットを利用することができます。

MUI System
<https://mui.com/system/basics/>

Palette
<https://mui.com/system/palette/>

#### `sx` Prop について

MUI v5 のすべてのコンポーネントは `sx` Prop でスタイリング可能です。`sx` Prop は従来の（React では非推奨ですが）`style` Prop に似ていますが、[MUI テーマに基づいたスタイリング](https://mui.com/system/the-sx-prop/#theme-aware-properties)や、[レスポンシブで動的なスタイリング](https://mui.com/system/basics/#responsive-values)が容易という特徴があります。

```tsx
<Box
  sx={{
    width: {
      xs: 100, // theme.breakpoints.up('xs')
      sm: 200, // theme.breakpoints.up('sm')
      md: 300, // theme.breakpoints.up('md')
      lg: 400, // theme.breakpoints.up('lg')
      xl: 500, // theme.breakpoints.up('xl')
    },
    height: (theme) => theme.spacing(10) // => 80px (8px * 10)
  }}
>
  This box has a responsive width.
</Box>
```

`sx` Prop では、v4 の `makeStyle` によるスタイリングと同様に、値に`theme` を引数にとった関数を指定することができます。

The sx prop
<https://mui.com/system/the-sx-prop/>

#### `<Box>` コンポーネントについて

[`<Box>` コンポーネント](https://mui.com/components/box/)は、`sx` Prop のプロパティを Prop として直接記述できる基礎的なコンポーネントです。実は [v4 時代には既に導入されていました](https://v4.mui.com/components/box/)が、パフォーマンスが低いという難点がありました。リリースノートによると、v5 では大幅にパフォーマンスが改善されたそうです。

```tsx
import Box from '@mui/material/Box';

// padding: 16px (spacing(8px) * 2)
// background-color: theme.palette.primary.dark
<Box p={2} bgcolor="primary.dark" width={100} height={100}>
  hoge
</Box>
```

`<Box>` コンポーネントと `<Typography>` コンポーネントではこの方法で、 `sx` Prop を使わずにスタイルを設定することができます。

`<Box>` Component
<https://mui.com/components/box/>

Properties
<https://mui.com/system/properties/>

#### 一般の HTML 要素のスタイリング

`<div>` や `<span>` などの一般の HTML 要素は `sx` Prop を持ちません。これらをスタイリングするには、`<Box>` コンポーネント や `<Typography>` コンポーネントを使い、`component` Prop で HTML 要素を指定するといいでしょう。

```tsx
import Box from '@mui/material/Box';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';

interface ItemProps {
  children: React.ReactNode;
}

function Item({ children }: ItemProps) {
  return (
    <Typography variant="body2" component="span" sx={{ backgroundColor: 'secondary.light', p: 1 }}>
      {children}
    </Typography>
  );
}

function Hoge() {
  return (
    // py (padding-top, padding-bottom): { xs: 16px, sm: 8px }
    <Box component="nav" sx={{ py: [2, 1] }}>
      <Stack spacing={2} direction={{ xs: 'column', sm: 'row' }}>
        <Item>Top</Item>
        <Item>News</Item>
        <Item>Site Map</Item>
      </Stack>
    </Box>
  );
}
```

`<Box>` Component
<https://mui.com/components/box/>

`<Typography>` Component
<https://mui.com/components/typography/>

#### margin, padding と `spacing` という単位

MUI にはスタイリングの統一感を出すために、padding や margin に指定するための `spacing` という単位が存在します。`spacing` は MUI テーマ で規定されていて、デフォルトでは 8px に設定されています。

`sx` Prop や `<Box>` コンポーネントで設定する padding や margin には、**`spacing` が単位として使われています**。v4 では `makeStyles` でフックを作る際に、`theme.spacing` を使うことで `spacing` の単位が明示的に使われていましたが、v5 になって `spacing` という単位が暗黙的に使われるようになったので注意が必要です。

```tsx
// margin-right: 8px (8px * 1)
<Typography component="span" sx={{ mr: 1 }}></Typography>

// padding-top, paading-bottom: 16px (8px * 2)
<Box py={2}></Box>
```

また、padding と margin だけは `spacing` を単位に取りますが、その他のプロパティは基本的に `px` を単位に取るので混同しないように注意が必要です。また、`borderRadius` は独自の `theme.shape.borderRadius` (デフォルトは `spacing` の半分である 4px)という単位を取るので注意しましょう。

```tsx
// 正円になる
<Box sx={{
  display: 'flex',
  width: 64, // => 64px
  height: 64, // => 64px
  borderRadius: 8, // => 32px (4px * 8)
}} />

<Box sx={{
  display: 'flex',
  width: (theme) => theme.spacing(8), // 64px
  height: (theme) => theme.spacing(8), // 64px 
  borderRadius: 8, // 32px (4px * 8)
}}>
```

Spacing
<https://mui.com/system/spacing/>

## 感想

v3 以前の `withStyles`、v4 の `makeStyles` といった Material-UI 特有の記法は Material-UI 入門者にはやや難解な印象がありました。
v5 では `sx` Prop で記述する形になったので、初心者にはわかりやすくなったものの、パレットカラーの記法や、padding, margin における `spacing` の暗黙的な利用など、覚えなくてはいけない事柄は増えた印象です。とはいえ、この辺りはおそらく [Theme UI] や [Tailwind CSS] でも同じことでしょう。

MUI v5 のスタイリングの触りの部分だけをざっと説明しました。
この先は[公式ドキュメント][MUI]、特に [MUI System](https://mui.com/system/basics/) の項を熟読することをお勧めします。

## 参考

- [MUI]
- [Introducing MUI Core v5.0](https://mui.com/blog/mui-core-v5/ "Introducing MUI Core v5.0")
- [Migration from v4 to v5](https://mui.com/guides/migration-v4/ "Migration from v4 to v5")
- [Material-UI v5を先取りする](https://tech.techtouch.jp/entry/material-ui-v5_9) - Techtouch Developers Blog

<!-- References -->
[Theme UI]: https://theme-ui.com "Theme UI"
[chakra]: https://chakra-ui.com "chakra"
[Tailwind CSS]: https://tailwindcss.com "Tailwind CSS"
[Qiita]: https://qiita.com/cieloazul310 "Qiita @cieloazul310"

[React]: https://reactjs.org/ "React"
[TypeScript]: https://www.typescriptlang.org "TypeScript"
[MUI]: https://mui.com "MUI"