Material UI v4のスタイル指定(JSS)

Material UIでのスタイル指定方法を説明します。Material UIのスタイルシステムの実体はJSSです。themeについては言及しません。もっと基礎的なところだけです。

注： 本記事の内容はMaterila UI v4に限定です。Material UI v5(MUI)はデフォルトでemotionベースになったので記述が異なります。

# classNameで即値指定

HTMLで言うclass=の指定。Material UI的には何もしない。BEMやscssでもなんでもいいのですが、外部スタイルシートを定義してあてがっていくパターン。

```jsx
<Hoge className="hoge fuga"/>
```

# styleで指定

styleプロパティの指定。Material UI的には何もしない。オブジェクトがdomのstyle属性に変換されて付けられる、Reactの仕組み。

```jsx
<Hoge style={{fontSize:1, padding: '1rem'} />
```

DOM上は、要素に直接紐付いたスタイル指定(devtool上、element.styleで表示される)となる。疑似要素は原理的に指定不可。

# StyledComponent💅で指定。

[こちら](https://material-ui.com/styles/api/#styled-component-styles-options-component)を参照。本資料では説明を割愛する。

# JSSのスタイルシートオブジェクトで指定

Material UIがかかわるのはここから。JSSは基本的にDOMのインラインスタイル属性ではなく、クラス名による指定の仕組みである。

## 大きな流れ

以下のような流れとなる。

1. スタイルシートの元になる「元のスタイルシートオブジェクト」をJSで作る```
const styles = {
  root: {
    backgroundColor: 'red',
  },
};```
2.  「元のスタイルシートオブジェクト」を元に、Material UIが準備しているHoCやhooksを使って、以下を生成する。
   * 「元のスタイルシートオブジェクト」のプロパティ名(「論理クラス名」とでも呼ぶとする)と、コンポーネントローカルにリネームされた実際に生成される「クラス名」(「物理クラス名」とでも呼ぶとする)の対応表。こんなやつ:
        `{"tabBar":"TabBar-tabBar-284","selectedTab":"TabBar-selectedTab-285"}`<br/>"tabBar"が論理クラス名、"TabBar-tabBar-284"が物理クラス名と本文書では呼ぶとする。物理クラス名をプログラマが意識することは(デバッグ時を除き)基本的にはない。
    * この物理クラス名でのCSSスタイル定義が、背後で&lt;head>に挿入される(プログラマは意識しないでよい)。
3. 「論理クラス名→物理クラス名の対応表」をMaterial UIが提供するReactコンポーネントにおいて以下のいずれかの方法で利用する
    * 対応表から物理クラス名を取得して「classNames」propsで指定する。
    * 対応表から物理クラス名を取得して「classes」propsで指定する。

## 「元のスタイルシートオブジェクト」の作りかた

### 単なるJSオブジェクトとして「スタイルシートオブジェクト」を作る

```jsx
const styles = {
  root: {
    backgroundColor: 'red',
  },
};
```

もしくは

```jsx
const styles = createStyles({
  root: {
    backgroundColor: 'red',
  },
});
```

両者の意味は同じである。後者ではTypeScriptの型エラーを黙らすことができる。

## themeを引数とする関数として「スタイルシートオブジェクト」を作る

```jsx
const styles = (theme: Theme) =>{
  root: {
    backgroundColor: theme.color.red,
  },
};
```

もしくは

```jsx
const styles = (theme: Theme) => createStyles({
  root: {
    backgroundColor: theme.color.red,
  },
});
```
両者の意味は同じである。後者ではTypeScriptの型エラーを黙らすことができる。

ここでは、Reactのインラインstyle属性のようにJavaScriptオブジェクトを用いるが、rootの階層が入っていることが異なる。この階層の名前は、(実CSSクラス名を隠蔽した)、ローカルなCSSクラス名と思ってよい。

CSSクラスなので疑似クラスも指定できる。たとえば、以下のように:hover疑似クラスが指定できる。(JSSの機能)

```jsx
const styles: any = (theme: Theme): StyleRules =>
  createStyles({
    button: {
      margin: '0.3rem',
      '&:hover': { transform: 'scale(1.1)' },
    },
```

## 「元のスタイルシートオブジェクト」から「クラス名の対応表」を入手する

### React Hooks系で

[こちらから](https://material-ui.com/styles/api/#makestyles-styles-options-hook)引用だが、以下のように「makeStyles」の引数に「元のスタイルシートオブジェクト」を渡すと、「クラス名の対応表」を取得できるフック関数を入手することができる。そのフック関数の呼び出し結果をclasses変数に取得している。(createStylesは使用していない。)

```jsx
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';

const useStyles = makeStyles({
  root: {
    backgroundColor: 'red',
    color: props => props.color,
  },
});

export default function MyComponent(props) {
  const classes = useStyles();
  return <div className={classes.root} />;
}

```

### HoC系で

HoCである[withStyles](https://material-ui.com/styles/api/#withstyles-styles-options-higher-order-component)を使う。こちらも引用だが以下となる。意味はHooks系と同じだが、「元のスタイルシートオブジェクト」をwithStylesの入力として与え、「クラス名の対応表」がprops.classesに得られるようなコンポーネントを生成している。

```jsx

import React from 'react';
import { withStyles } from '@material-ui/core/styles';

const styles = {
  root: {
    backgroundColor: 'red',
  },
};

function MyComponent(props) {
  return <div className={props.classes.root} />;
}

export default withStyles(styles)(MyComponent);
```

## 取得した「クラス名の対応表」の使いかた

「クラス名の対応表」は、入手方法としてHooksでもHoCで得ることができるが、同じものである。上記サンプルでは、入手した「クラス名の対応表」はclassesという名前のprops(props.classes)もしくは変数classesに格納している。

「クラス名の対応表」には2つの使いかたがある。

## classNameに与える

サンプルにもあるが、

```jsx
  return <div className={classes.root} />;
```

のように指定する。複数あれば

```jsx
  return <div className={classNames(classes.root,classes.hoge)} />;
```

のようにNPMモジュールclassnamesを使うのがよいだろう。classnamesはクラス名を結合させているだけである。([clsx](https://qiita.com/taqm/items/c38855d8158cdd9d5a3e)というのもあり効率がよいらしい)。

## classesで指定

最後になったが、これがJSSの本領である。Material UIのコンポーネントにはCSS APIというものが定義されており、それを使ってカスタマイズすることができる。
たとえば、[Tabsコンポーネントには以下のようにかかれている](https://material-ui.com/api/tabs/)。

***CSS***
- Style sheet name: MuiTab.
- Style sheet details:

| Rule name | Global class | Description |
|:--|:--|:--|
| root | .MuiTab-root | Styles applied to the root element. |
| labelIcon | .MuiTab-labelIcon | Styles applied to the root element if both icon and label are provided. |
| textColorInherit | .MuiTab-textColorInherit | Styles applied to the root element if the parent Tabs has textColor="inherit". |
| textColorPrimary | .MuiTab-textColorPrimary | Styles applied to the root element if the parent Tabs has textColor="primary". |
| textColorSecondary | .MuiTab-textColorSecondary | Styles applied to the root element if the parent Tabs has textColor="secondary". |
| <font color="red">selected</font> | .Mui-selected | Pseudo-class applied to the root element if selected={true} (controlled by the Tabs component). |

これらを必要に応じてオーバーライド定義することができる。赤字"selected"は後述の説明で例として使用するCSS APIのキー名である。このスタイルをオーバーライドする方法は3つある。

- With a rule name of the classes object prop.(classes propsのルール名)
- With a global class name. (グローバルクラス名)
- With a theme and an overrides property..(テーマと上書きプロパティ)

以降では最初の「 rule name of the classes object prop.」について説明する。
やるべきことは、classes属性に、指定したCSS API名をキー(ここではslected)とし「クラス名の対応表」をひっぱって物理クラス名を指定するのである。

```jsx
const styles = (theme: any) =>
  createStyles({
    selectedTab: {
      backgroundColor: '#eeeeff',
      fontWeight: 'bold',
    },
  });
:
          <Tabs>
            <Tab
              classes={{ selected: classes.selectedTab }} />
               :
          </Tabs>
```
するともともとMUIで定義されていたCSSに上書きされる形でCSSスタイルが定義される。

MUIコンポーネントのclasses属性は、それぞれのMUIコンポーネントが内部的に持っていて使用している「元のスタイルシートオブジェクト」のカスタマイズのための「置き換え対応表」として使用される。