React: Material-UI 4.0のコンポーネントへのCSS設定はwithStyles()からmakeStyles()に

[Material-UI](https://material-ui.com/)は、Googleの提唱する[マテリアルデザイン](https://material.io/design/)を実装したReactコンポーネントです。また、CSSのスタイルシートをJavaScriptコードに書き込むCSS-in-JSを採用しました。そして、そのスタイルシートをコンポーネントに提供する関数が`makeStyles()`です。本稿では、この関数を使って、Reactの簡単なアプリケーションの関数コンポーネントにスタイルシートを定めてみます。

これまでスタイルの定めには、コンポーネントをラップする`withStyles()`関数が用いられました(「[React: Material-UIのwithStyles()でCSSをJavaScriptコードに定める](https://qiita.com/fumio_nonaka/items/2cac41d10f2bd8343633)」参照)。けれど、公式サイトのblog記事「[March 2019 Update](https://material-ui.com/blog/march-2019-update/)」では、できるだけ`makeStyles()`を使うことが推奨されています。

> We have migrated a few demos from the `withStyles()` API to the `makeStyles()` API. If you are wondering which you should use, we would encourage the use of `makeStyles()` where possible.


# ひな形のアプリケーションにインストールする
Reactアプリケーションのひな形は、[`create-react-app`](https://facebook.github.io/create-react-app/)でつくりましょう。コマンドラインツールからつぎのように入力します。

```terminal
$ npx create-react-app my-app
```

アプリケーション(`my-app`)のディレクトリに切り替えたら、つぎに`material-ui`をインストールしてください。

```terminal
$ npm install @material-ui/core
```

これで、Material-UIを使う準備が整いました。


# makeStyles()の構文
ReactコンポーネントにJavaScriptのオブジェクトで定めたスタイルシートを提供する関数[`makeStyles()`](https://material-ui.com/styles/api/#makestyles-styles-options-hook)の構文を先にご紹介しましょう。

### `makeStyles(styles, [options]) => hook`
戻り値の関数から、コンポーネントに関連づけるスタイルシートのオブジェクトがつくられます。

#### 引数
- `styles`(`Function | Object`)  
スタイルをつくる関数、またはスタイルオブジェクトです。コンポーネントに関連づけられます。テーマを参照する場合には、関数で定めて、第1引数に渡してください。
- `options`(`Object`[省略可能])
    - `options.defaultTheme`(`Object `[省略可能]): デフォルトの`theme`オブジェクトです。[`ThemeProvider`](https://material-ui.com/styles/api/#themeprovider)から提供されない場合に用いられます。
    - `options.withTheme`(`Boolean`[省略可能]): `theme`オブジェクトをコンポーネントにプロパティとして与えるかどうかの論理値です(デフォルト値は`false`)。
    - `options.name`(`String`[省略可能]): スタイルシートの名前です。デバッグに役立ちます。値がなければ、コンポーネント名へのフォールバックが試みられます。
    - `options.flip`(`Boolean`[省略可能]): `false`にすると、スタイルシートの[`rtl`(Right-to-left)変換](https://material-ui.com/guides/right-to-left/)はオプトアウトされます。`true`は、スタイルが左右反転します。`null`の場合は、`theme.direction`にしたがいます。
    - その他のキーは、[`jss.createStyleSheet([styles], [options])`](https://cssinjs.org/jss-api?v=v10.0.0-alpha.7#create-style-sheet)の引数`options`に渡されます。

#### 戻り値
- `hook`  
スタイルシートのオブジェクトをつくるフック関数です。関数コンポーネントの中でも使えます(「[関数コンポーネントとクラスコンポーネント](https://ja.reactjs.org/docs/components-and-props.html#function-and-class-components)」参照)。[オプションの引数](https://material-ui.com/styles/advanced/#makestyles)には、スタイルシートで用いるプロパティを納めたオブジェクトが渡せます。

# CSSのスタイルをJavaScriptのオブジェクトに移す
`create-react-app`でつくられたReactアプリケーション(`src/App.js`)に、`makeStyles()`を`import`します。そして、CSSファイル(`src/App.css`)は除いてしまいましょう。これで、一旦スタイルシートは適用されなくなりました。

```react:src/App.js
// import './App.css';
import { makeStyles } from '@material-ui/styles';
```

まずは、CSSファイル(`src/App.css`)からクラス(`App`)の定めをひとつ、JavaScriptコードに移してみます。

```css:src/App.css
.App {
  text-align: center;
}
```

関数の引数に渡すスタイルシートはオブジェクトで定めます。クラス名はプロパティとし、設定は入れ子のオブジェクトです。その中にプロパティと文字列の設定値を加えるかたちになります。

```js
const フック関数 = makeStyles({
  クラス: {
    プロパティ: 文字列の設定値,
    // 他のプロパティの定め
  },
  // 他のクラス
});
```

テーマを参照する場合には、スタイルシートを関数で定めます。引数にテーマ(`theme`)を受け取り、戻り値がスタイルシートオブジェクトです。

```js
const フック関数 = makeStyles((theme) => ({
  クラス: {
    プロパティ: 文字列の設定値,
    // 他のプロパティの定め
  },
  // 他のクラス
}));
```

今回は、簡単にオブジェクトで定めましょう。ここで、JavaScriptのプロパティにはハイフン(`-`)が使えないことにご注意ください。以下のようにクラスやプロパティのハイフンは除いて、キャメルケースにしなければなりません。そのスタイルシートを`makeStyles()`の引数にして呼び出すと、フック関数(`useStyles`)が得られます。

関数コンポーネントの中でフック関数を呼び出せば、`class`オブジェクトがつくられられます。スタイルシートに定めたクラスの参照を取り出して、`render()`が返すマークアップ(テンプレート)の`className`属性に与えればよいのです。

```react:src/App.js
const useStyles = makeStyles({
  app: {
    textAlign: 'center'
  }
});

function App() {
  const classes = useStyles();
  return (
    // <div className="App">
    <div className={classes.app}>

    </div>
  );
}
```

これで、JavaScriptコードに定めたCSSのクラスがひとつReactアプリケーション(`src/App.js`)に割り当てられました。同じ要領で、CSSファイル(`src/App.css`)の他のクラスも移していきましょう。ただし、`animation`が使われているクラス(`App-logo`)はあとに回します。

```css:src/App.css
.App-header {
  background-color: #282c34;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  color: white;
}

.App-link {
  color: #61dafb;
}
```

クラスとプロパティの名前はキャメルケースにし、CSSの値が文字列、そしてプロパティはカンマ(`,`)区切りになることに注意してください。

```react:src/App.js
const useStyles = makeStyles({

  appHheader: {
    backgroundColor: '#282c34',
    minHeight: '100vh',
    display: 'flex',
    flexDirection: 'column',
    alignItems: 'center',
    justifyContent: 'center',
    fontSize: 'calc(10px + 2vmin)',
    color: 'white'
  },
  appLink: {
    color: '#61dafb'
  }
});

function App() {
  const classes = useStyles();
  return (
    <div className={classes.app}>
      {/* <header className="App-header"> */}
      <header className={classes.appHheader}>

        <a
          // className="App-link"
          className={classes.appLink}

        >

        </a>
      </header>
    </div>
  );
}
```

これで、アニメーションする要素(`<img>`)以外のスタイルは整いました。

# `animation`を定める
`animation`プロパティには`@keyframes`規則が加えられています。これは、
`@keyframes キーフレーム名`までをひとつの文字列としてスタイルオブジェクトのプロパティに定めてください。

```css:src/App.css
.App-logo {
  animation: App-logo-spin infinite 20s linear;
  height: 40vmin;
  pointer-events: none;
}

@keyframes App-logo-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}
```

`@keyframes`規則の中身は、つぎのように入れ子のオブジェクトです。パーセンテージ(`%`)を用いる場合には、はじめが数字になるため文字列で定めなければなりません。そして、`animation`プロパティに定める`@keyframes`規則の名前には、頭に`$`印をつけてください(JSS「[Keyframes Animation](https://cssinjs.org/jss-syntax/#keyframes-animation)」)[^1]。


```react:src/App.js
const useStyles = makeStyles({

  appLogo: {
    animation: '$App-logo-spin infinite 20s linear',
    height: '40vmin',
    pointeEvents: 'none'
  },

  '@keyframes App-logo-spin': {
    from: {
      transform: 'rotate(0deg)'
    },
    to: {
      transform: 'rotate(360deg)'
    }
  }
});

function App() {
  const classes = useStyles();
  return (
    <div className={classes.app}>
      <header className={classes.appHheader}>
        {/* <img src={logo} className="App-logo" alt="logo" /> */}
        <img src={logo} className={classes.appLogo} alt="logo" />

      </header>
    </div>
  );
}
```

これでひな形Reactアプリケーションのスタイルシートが、すべてJavaScriptコードに移せました。CSSファイル(`src/App.css`)はもう要りません。JavaScriptファイル(`src/App.js`)の中身を改めてつぎのコード001にまとめて掲げます。

#### コード001■`src/App.js`
```react:src/App.js
import React from 'react';
import logo from './logo.svg';
import { makeStyles } from '@material-ui/styles';

const useStyles = makeStyles({
  app: {
    textAlign: 'center'
  },
  appLogo: {
    animation: '$App-logo-spin infinite 20s linear',
    height: '40vmin',
    pointeEvents: 'none'
  },
  appHheader: {
    backgroundColor: '#282c34',
    minHeight: '100vh',
    display: 'flex',
    flexDirection: 'column',
    alignItems: 'center',
    justifyContent: 'center',
    fontSize: 'calc(10px + 2vmin)',
    color: 'white'
  },
  appLink: {
    color: '#61dafb'
  },
  '@keyframes App-logo-spin': {
    from: {
      transform: 'rotate(0deg)'
    },
    to: {
      transform: 'rotate(360deg)'
    }
  }
});

function App() {
  const classes = useStyles();
  return (
    <div className={classes.app}>
      <header className={classes.appHheader}>
        <img src={logo} className={classes.appLogo} alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className={classes.appLink}
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;
```


[^1]: JSSのバージョンによっては、`$`をつけないこともあるようです(「[Do keyframe animations work?](https://github.com/mui-org/material-ui/issues/13793#issuecomment-480481417)」参照)。