JavaScript
React
material-ui
css-in-js

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

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

ひな形のアプリケーションにインストールする

Reactアプリケーションのひな形は、create-react-appでつくりましょう。コマンドラインツールからつぎのように入力します。

$ npx create-react-app my-app

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

$ npm install @material-ui/core

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

withStyles()の構文

ReactコンポーネントにJavaScriptコードのスタイルシートを組み込む関数withStyles()の構文を先にご紹介しましょう。

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)変換はオプトアウトされます。trueは、スタイルが左右反転します。nullの場合は、theme.directionにしたがいます。
    • その他のキーは、jss.createStyleSheet([styles], [options])の引数optionsに渡されます。

戻り値

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

CSSのスタイルをJavaScriptコードに移す

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

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

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

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

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

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

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

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

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

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

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)はあとに回します。

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の値が文字列、そしてプロパティはカンマ(,)区切りになることに注意してください。

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 キーフレーム名までをひとつの文字列としてスタイルオブジェクトのプロパティに定めてください。

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

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)の中身を改めて以下にまとめて掲げます。

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);