React: Material-UIのwithStyles()でCSSをJavaScriptコードに定める

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

[追記: 2019/06/05] 公式サイトのblog記事「[March 2019 Update](https://material-ui.com/blog/march-2019-update/)」では、できるだけ`makeStyles()`関数を使うことが推奨されています。この関数の使い方については、「[React: Material-UI 4.0のコンポーネントへのCSS設定はwithStyles()からmakeStyles()に](https://qiita.com/fumio_nonaka/items/9e0f3172b8f85e93cbbe)」をお読みください。

> 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.

`withStyles()`を使う例としては、クラスコンポーネントへの適用が考えられます(「[How to style Class Components using makeStyles in Material UI?](https://stackoverflow.com/questions/56432167/how-to-style-class-components-using-makestyles-in-material-ui/56606089#56606089)」参照)。


# ひな形のアプリケーションにインストールする
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を使う準備が整いました。


# withStyles()の構文
ReactコンポーネントにJavaScriptコードのスタイルシートを組み込む関数[`withStyles()`](https://material-ui.com/styles/api/#withstyles-styles-options-higher-order-component)の構文を先にご紹介しましょう。

### `withStyles(styles, [options]) => higher-order component`
スタイルシートをコンポーネントに関連づけます。引数に渡したコンポーネントそのものは変わりません。戻り値は、プロパティ`classes`が備わった新たなコンポーネントです。`classes`オブジェクトには、DOMに加えられたCSSのクラス名が納められています。

#### 引数
- `styles`(`Function | Object`)  
スタイルをつくる関数、またはスタイルオブジェクトです。コンポーネントに関連づけられます。テーマを参照する場合には、関数で定めてください。
- `options`(`Object`[省略可能])
    - `options.withTheme`(`Boolean`[省略可能]): デフォルト値は`false`です。`theme`オブジェクトをコンポーネントにプロパティとして与えます。
    - `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`に渡されます。

#### 戻り値
- higher-order component  
ラップされてプロパティclassesが備わったコンポーネントです。


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

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

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

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

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

```react
const スタイルシート = {
	クラス: {
		プロパティ: 文字列の設定値,
		// 他のプロパティの定め
	},
	// 他のクラス
}
```

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

```react
const スタイルシート = (theme) => {
	クラス: {
		プロパティ: 文字列の設定値,
		// 他のプロパティの定め
	},
	// 他のクラス
}
```

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

すると、プロパティ`props`に`classes`が備わって、スタイルシートに定めたクラスを参照できるようになります。それを`render()`が返すマークアップ(テンプレート)の`className`に与えればよいのです。

```react:src/App.js
const styles = {
	app: {
		textAlign: 'center'
	}
}
class App extends Component {
	render() {
		return (
			<div className={this.props.classes.app}>

			</div>
		);
	}
}

// export default App;
export default withStyles(styles)(App);
```

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

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

.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 styles = {

	appHeader: {
		backgroundColor: '#282c34',
		minHeight: '100vh',
		display: 'flex',
		flexDirection: 'column',
		alignItems: 'center',
		justifyContent: 'center',
		fontSize: 'calc(10px + 2vmin)',
		color: 'white'
	},
	appLink: {
		color: '#61dafb'
	}
};
class App extends Component {
	render() {
		return (
			<div className={this.props.classes.app}>
				<header className={this.props.classes.appHeader}>

					<a
						className={this.props.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;
}

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

`@keyframes`規則の中身は、つぎのように入れ子のオブジェクトです。パーセンテージ(`%`)を用いる場合には、頭が数字になるため文字列で定めてください。

```react:src/App.js
const styles = {

	'@keyframes App-logo-spin': {
		from: {
			transform: 'rotate(0deg)'
		},
		to: {
			transform: 'rotate(360deg)'
		}
	},
	appLogo: {
		animation: 'App-logo-spin infinite 20s linear',
		height: '40vmin'
	},

};
class App extends Component {
	render() {
		return (
			<div className={this.props.classes.app}>
				<header className={this.props.classes.appHeader}>
					<img src={logo} className={this.props.classes.appLogo} alt="logo" />

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

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

```react:src/App.js
import React, { Component } from 'react';
import { withStyles } from '@material-ui/core/styles';
import logo from './logo.svg';

const styles = {
	app: {
		textAlign: 'center'
	},
	'@keyframes App-logo-spin': {
		from: {
			transform: 'rotate(0deg)'
		},
		to: {
			transform: 'rotate(360deg)'
		}
	},
	appLogo: {
		animation: 'App-logo-spin infinite 20s linear',
		height: '40vmin'
	},
	appHeader: {
		backgroundColor: '#282c34',
		minHeight: '100vh',
		display: 'flex',
		flexDirection: 'column',
		alignItems: 'center',
		justifyContent: 'center',
		fontSize: 'calc(10px + 2vmin)',
		color: 'white'
	},
	appLink: {
		color: '#61dafb'
	}
};
class App extends Component {
	render() {
		return (
			<div className={this.props.classes.app}>
				<header className={this.props.classes.appHeader}>
					<img src={logo} className={this.props.classes.appLogo} alt="logo" />
					<p>
						Edit <code>src/App.js</code> and save to reload.
					</p>
					<a
						className={this.props.classes.appLink}
						href="https://reactjs.org"
						target="_blank"
						rel="noopener noreferrer"
					>
						Learn React
					</a>
				</header>
			</div>
		);
	}
}

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