TypeScript + Material-UI v4 のスタイル付きコンポーネント作成ガイド

2019年5月下旬に [Material-UI v4][Material-UI] が正式にリリースされました。スタイル付きコンポーネントの記法が v3 以前のものと変わったのでメモしておきます。

### **加筆(2019/12/21)**

この記法が、React 16.8 で導入された[フック(hook)][フックの導入]であることを補足しました。

### **加筆(2021/09/20)**

[MUI (Material-UI) v5](https://mui.com/blog/mui-core-v5/) がリリースされました。
MUI v5 のスタイリング方法については⬇️をどうぞ

https://qiita.com/cieloazul310/items/d630da98439c89d773ba

## 環境

`create-react-app` で作成したディレクトリを前提にしています。

参考: [TypeScript React Starter](https://github.com/Microsoft/TypeScript-React-Starter#typescript-react-starter)

## バージョン

```txt
- typescript @3.2.2
- @material-ui/core @4.0.2
- react @16.8.6
- react-dom @16.8.6
```

## v3 からの変更点

v3 でのスタイル付きコンポーネントの記法は以下の記事を参照してください。
v4 で初めて Material-UI を使うという人は読む必要はありません。

[TypeScript + React + Material-UI v3 のスタイル付き Components ガイド]

- `withStyles(styles)(Component)` という記法ではなくなった(互換性はあるため v4 でも利用可能)
- 代わりに `makeStyles` という関数を使ってスタイルを定義する
- className を `props` で与えるのではなくコンポーネント内で取得する

以下の記法は、React 16.8 で追加された [フック(hook)][フックの導入] を使っています。

## Material-UI v4 での記法

### 例

スタイル付きコンポーネント作成の一例です。

```tsx:MyStyledComponent.tsx
import * as React from 'react';
import { makeStyles, createStyles, Theme } from '@material-ui/core/styles';

// スタイルを定義
const useStyles = makeStyles((theme: Theme) =>
  createStyles({
    root: {
      padding: theme.spacing(2)
    },
    title: {
      borderBottom: `2px solid ${theme.palette.primary.main}`
    }
  })
);

// props の型を定義
type Props = {
  title?: string;
};

// コンポーネントを定義
function MyStyledComponent({ title }: Props) {
  // ここでクラス名を取得
  const classes = useStyles();
  return (
    <div className={classes.root}>
      <h4 className={classes.title}>
        {title || 'My Styled Component'}
      </h4>
    </div>
  );
}

// エクスポート
export default MyStyledComponent;
```

[React & Redux in TypeScript - Static Typing Guide] を参考にしています。

### 1. インポート

```tsx
// JavaScript の場合は createStyles, Theme は不要
import { makeStyles, createStyles, Theme } from '@material-ui/core/styles';
```

`createStyles` は型拡大を防ぐ関数で TypeScript のときのみ任意で使用します。
`Theme` は Material テーマ (theme) の 型定義で、これを使うことでエディタの補助機能が効き入力が楽になります。

### 2. フック `useStyles` を定義

```tsx
// JavaScript の場合は makeStyles(theme => styleObject)で良い
const useStyles = makeStyles((theme: Theme) =>
  createStyles({
    root: {
      padding: theme.spacing(2)
    },
    title: {
      borderBottom: `2px solid ${theme.palette.primary.main}`
    }
  })
);
```

ここで定義した `useStyles` は `makeStyles` で定義したスタイルオブジェクトの key と className の組を返すフックになります。

```tsx
const classes = useStyles();
console.log(classes);
// => Object { root: "useStyles-root-41", title: "useStyles-title-56" }
```

### 3. コンポーネントの中で className を取得

```tsx
function MyStyledComponent({ title }: Props) {
  const classes = useStyles();
  return (
    <div className={classes.root}>
      <h4 className={classes.title}>
        {title || 'タイトル'}
      </h4>
    </div>
  );
}
```

v3 のときは、コンポーネントの `props` で className を与えていましたが、v4 ではコンポーネントの中でフックを使ってクラス名を取得します。 
これによって、コンポーネントの `props` の型定義 `Props` を拡張する必要がなくなりました。

したがって、v3 時代の `Props extends WithStyles<typeof styles>` や `export default withStyles(styles)(Component)` という煩雑な書き方がなくなりすっきりとした表現になっています。

### v3 以前からv4 への移行の注意点

v3 時代の `withStyles(styles)(Component)` という書き方は v4 でも可能です。
互換性がない変更点では、padding などを指定する際に使う [`theme.spacing` が関数になりました](https://material-ui.com/customization/spacing/)。したがって、v3 以前の `theme.spacing.unit * 2` という書き方ではエラーを吐いてしまいます。v3 から v4 へ移行するならまず `theme.spacing` の書き方を変更しましょう。

```tsx
const useStyles = makeStyles((theme: Theme) => createStyles({
  root: {
    // v3 時代は theme.spacing.unit * 2 という書き方だった
    padding: theme.spacing(2)
  }
});
```


## 補足: React の記法について

ここは Material-UI に関係ないので読み飛ばしても構いません。
React + TypeScript の記法の流行り(?)に関する話題です。**最終的には好みの問題**になります。

### Function Component で `const` と `function` どちらを使うべきか

```tsx
const MyComponent: React.FC<Props> = ({ title }: Props) => (
  <div>{ title }</div>
);
export default MyComponent;
```

```tsx

function MyComponent({ title }: Props) {
  return (
    <div>{ title }</div>
  );
}
export default MyComponent;
```

最近は `function` 記法を目にすることが多いです。特に理由がなければ、`React.FC<Props>` はもう使わなくていいかもしれません。

- [React 公式ドキュメント][React] => `function`
- [React & Redux in TypeScript - Static Typing Guide] => `function`
- [Material-UI ドキュメント][Material-UI] => `function`

また State を使う場合でも、クラス型コンポーネントを使わずに、React 16.8 で導入された [フック](https://reactjs.org/docs/hooks-intro.html) で書いているドキュメントが多いです。

### `Props` の定義は `type` か `interface` か

```ts
interface Props {
  title?: string;
}
```

```ts
type Props = {
  title?: string;
}
```

最近は `type` を使うのをよく目にします。
また state を使用する場合でも、 フックを使って、型定義 `State` を定義しない書き方を多く目にします。

最終的には好みの問題なので、自分のやりやすいように書いてください。

## まとめ

ここでは手っ取り早く Material-UI v4 でのスタイル付きコンポーネントの記法について書きました。v4 についてもっと知りたい方は下記の Medium を読むと理解が深まるかと思います。

Material-UI v4 is out
https://medium.com/material-ui/material-ui-v4-is-out-4b7587d1e701

Material-UI
https://material-ui.com

React & Redux in TypeScript - Static Typing Guide
https://github.com/piotrwitek/react-redux-typescript-guide

フックの導入
https://ja.reactjs.org/docs/hooks-intro.html

フックに関するよくある質問
https://ja.reactjs.org/docs/hooks-faq.html

<!-- References -->
[TypeScript + React + Material-UI v3 のスタイル付き Components ガイド]: https://qiita.com/cieloazul310/items/ec52ac3b7a673d980f2c

[React & Redux in TypeScript - Static Typing Guide]: https://github.com/piotrwitek/react-redux-typescript-guide

[Qiita]: https://qiita.com/cieloazul310
[React]: https://reactjs.org/
[create-react-app]: https://github.com/facebook/create-react-app

[Material-UI]: https://material-ui.com

[フックの導入]: https://ja.reactjs.org/docs/hooks-intro.html
[フックに関するよくある質問]: https://ja.reactjs.org/docs/hooks-faq.html