Hirooooo’s Labo

開発メモ、ガジェット、日記、趣味など、思った事を思ったまんま書くブログ

React × Redux 初心者入門(4日目:Material-UI(v1)を実装する) ver.2018

f:id:hirooooo-lab:20160713002241j:plain

前回までで簡単なカウンターアプリがReact Reduxで出来上がっている状態なので、次はデザイン周りの実装をしていきたいと思います。
今回実装するのはMaterial-UIを追加してみたいと思います。

こちらは昔書いた記事のリライト版になりますので、記事の文言等々はご了承ください。

公式サイト

MaterialUIの公式サイトは一通り見ておきましょう。

www.material-ui.com

material-ui v1のインストール

現時点(2018/01)では、v0.20.0が最新バージョンになっています。
しかしながら、サイトを確認すると、上部にデカデカと書いてあります。
Aww yeah, Material-UI v1 is coming!
f:id:hirooooo-lab:20180119180551p:plain

なので、今回は先取りしてMaterial-UI v1を使用していきたいと思います。

ターミナルから以下のnpmコマンドでパッケージをインストールします。

$ npm install --save material-ui@next

SVGIconを追加する

material-uiのv1からSVGIconが別のパッケージに分離されてしまったので、こちらも追加でインストールします。

$ npm install --save material-ui-icons

これで必要なモジュールのインストールは完了です。

MuiThemeを読みこませる

テーマファイルの作成

material-uiで使用するテーマファイルを作成します。
公式サイトを見てもらうとわかるんですが、せっかくなので独自のテーマファイルを作って設定してみたいと思います。

Themes - Material-UI

src/myThemeFile.jsx

以下のsrc/myThemeFile.jsxを作成しましょう。

import { createMuiTheme } from 'material-ui/styles';
import indigo from 'material-ui/colors/indigo';
import pink from 'material-ui/colors/pink';
import red from 'material-ui/colors/red';

// All the following keys are optional.
// We try our best to provide a great default value.
const myTheme = createMuiTheme({
  palette: {
    contrastThreshold: 3,
    tonalOffset: 0.2,
    primary: indigo,
    secondary: pink,
    error: {
      main: red[500],
    },
  },
});

export default myTheme;

App.jsxでテーマを読み込む

containers/App.jsxにThemeProviderを追加して作成したテーマを反映させます。
以下のソースを追加します。

import { MuiThemeProvider } from 'material-ui/styles';
import myTheme from '../src/theme/myThemeFile';

....( 省略)....
  render() {
    const { counter, actions } = this.props;
    return (
      <MuiThemeProvider theme={myTheme}>
        <div>
          <Header />
          <h2>count={counter.value}</h2>
          <button onClick={actions.increment}>増加</button>
          <button onClick={actions.decrement}>減少</button>
        </div>
      </MuiThemeProvider>
    );
  }
....( 省略)....

}

ThemeProviderについては公式のこの辺りの例を参照ください。

Themes - Material-UI

これで準備は整ったので、実際にコンポーネントを変更してみます。

HeaderをAppBarにしてみる

Header.jsxにAppBarを追加する

前回のHeader.jsxにAppBarコンポーネントを追加してみます。
必要なコンポーネントのimportを追加して、前回の

  <header className="header">
    <h1>ヘッダだZE</h1> {/* YOをZEに変更 */}
  </header>

の部分を

      <header className="header">
        <AppBar position="static">
          <Toolbar>
            <IconButton color="inherit">
              <MenuIcon />
            </IconButton>
            <Typography type="title" color="inherit" className={classes.flex}>
              カウンターAPP
            </Typography>
            <Button color="inherit" className={classes.menuButton}>Login</Button>
          </Toolbar>
        </AppBar>
      </header>

こう変更します。

AppBarコンポーネントについては、公式サイトのデモのまま記載しています。
詳しくはこちら

さらに、AppBarで使用しているスタイルの設定では、v1からJSSを採用して設定するようになっています。
これも公式サイトに習って実装していきます。

JSSスタイルの反映

1.withStylesのimport

import { withStyles } from 'material-ui/styles';

を追加します。

2.styleの作成

const styles = {
  root: {
    width: '100%',
  },
  flex: {
    flex: 1,
  },
  menuButton: {
    marginLeft: -12,
    marginRight: 20,
  },
};

を追加します。

3.ピュアファンクションだったHeaderをComponentにし、withStylesでラップします。

import React, { PureComponent } from 'react';

const Header = () => ( // これを
↓
class Header extends PureComponent { // こう書き換える

export default Header;  // これを
↓
export default withStyles(styles)(Header); // こう書き換える

4.PropTypesの定義追加

PropTypesを追加して、propsの定義を追加します。

import PropTypes from 'prop-types';

  static propTypes = {
    classes: PropTypes.object.isRequired,
  };

を追加します。

Header.jsxの全体像

import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import AppBar from 'material-ui/AppBar';
import Toolbar from 'material-ui/Toolbar';
import Typography from 'material-ui/Typography';
import Button from 'material-ui/Button';
import IconButton from 'material-ui/IconButton';
import MenuIcon from 'material-ui-icons/Menu';
import { withStyles } from 'material-ui/styles';


const styles = {
  root: {
    width: '100%',
  },
  flex: {
    flex: 1,
  },
  menuButton: {
    marginLeft: -12,
    marginRight: 20,
  },
};

class Header extends PureComponent {
  static propTypes = {
    classes: PropTypes.object.isRequired,
  };

  render() {
    const { classes } = this.props;
    return (
      <header className="header">
        <AppBar position="static">
          <Toolbar>
            <IconButton color="inherit">
              <MenuIcon />
            </IconButton>
            <Typography type="title" color="inherit" className={classes.flex}>
              カウンターAPP
            </Typography>
            <Button color="inherit" className={classes.menuButton}>Login</Button>
          </Toolbar>
        </AppBar>
      </header>
    );
  }
}

export default withStyles(styles)(Header);

実行してみる

ターミナルでnpm startを打ってみてください。

$ npm start

こんなページになったらOKです。
うん。素敵!
f:id:hirooooo-lab:20180126124738p:plain

ボタンを変更してみる

ボタンの変更をする前に、containers/App.jsxに直接カウンターの部分が書いてあるので、コンポーネントに切り分けたいと思います。

components/Counter.jsxの作成

components/Counter.jsxcontainers/App.jsxからカウンターの部分を抜き出します。

import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';

class Counter extends PureComponent {
  static propTypes = {
    counter: PropTypes.object.isRequired,
    actions: PropTypes.object.isRequired,
  };

  render() {
    const { counter, actions } = this.props;
    return (
      <div>
        <h2>count={counter.value}</h2>
        <button onClick={actions.increment}>増加</button>
        <button onClick={actions.decrement}>減少</button>
      </div>
    );
  }
}

export default Counter;

containers/App.jsxの修正

components/Counterをimportして、ボタンが直書きされていた箇所に追加します。

import Counter from '../components/Counter'
....(省略)....
  render() {
    const { counter, actions } = this.props;
    return (
      <MuiThemeProvider theme={myTheme}>
        <div>
          <Header />
          <Counter counter={counter} actions={actions} />
        </div>
      </MuiThemeProvider>
    );
  }
....(省略)....

valueとactionsはCounterコンポーネントのpropsに流し込んで上げましょう。

containers/App.jsxの全体像

import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { hot } from 'react-hot-loader';
import { MuiThemeProvider } from 'material-ui/styles';

import Header from '../components/Header';
import Counter from '../components/Counter';
import CounterActions from '../actions/counter';
import myTheme from '../src/theme/myThemeFile';

class App extends PureComponent {
  static propTypes = {
    counter: PropTypes.object.isRequired,
    actions: PropTypes.object.isRequired,
  };

  render() {
    const { counter, actions } = this.props;
    return (
      <MuiThemeProvider theme={myTheme}>
        <div>
          <Header />
          <Counter counter={counter} actions={actions} />
        </div>
      </MuiThemeProvider>
    );
  }
}

// Appコンポーネントにstateを流し込む
function mapStateToProps(state) {
  return {
    counter: state.counter,
  };
}

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(Object.assign({}, CounterActions), dispatch),
  };
}

export default hot(module)(connect(
  mapStateToProps,
  mapDispatchToProps,
)(App));

実行してみる

npm startでさっきと変わらず表示されればOKです。

components/Counter.jsxの修正

いよいよMaterial-UIのButtonに変更してみたいと思います。

必要なコンポーネントをインポート

import Button from 'material-ui/Button';
import AddIcon from 'material-ui-icons/AddCircle';
import RemoveIcon from 'material-ui-icons/RemoveCircle';
import { withStyles } from 'material-ui/styles';

components/Counter.jsx全体像

import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import Button from 'material-ui/Button';
import AddIcon from 'material-ui-icons/AddCircle';
import RemoveIcon from 'material-ui-icons/RemoveCircle';
import { withStyles } from 'material-ui/styles';


const styles = {
  button: {
    margin: 5,
  },
};

class Counter extends PureComponent {
  static propTypes = {
    counter: PropTypes.object.isRequired,
    actions: PropTypes.object.isRequired,
    classes: PropTypes.object.isRequired,
  };

  render() {
    const { counter, actions, classes } = this.props;
    return (
      <div>
        <h2>count={counter.value}</h2>
        <Button raised color="primary" className={classes.button} onClick={actions.increment}>
          増加
          <AddIcon />
        </Button>
        <Button raised color="secondary" className={classes.button} onClick={actions.decrement}>
          減少
          <RemoveIcon />
        </Button>
      </div>
    );
  }
}

export default withStyles(styles)(Counter);

実行結果

こんな感じになりました

f:id:hirooooo-lab:20180126151707p:plain

まとめ

結構簡単にmaterial-uiが実装できると思います。
基本的に公式サイトに乗ってあるようなことしかやってないので、ちゃんと公式サイトを読めば問題なく使えるようになると思います。

以前の古い記事はこちら

www.hirooooo-lab.com

次回予告

次回予告というか、あとやらなきゃいけないと思ってること

  • 開発環境の効率化をするためにHotReloadとソースマップを実装する → Done!
  • ESLintでAirbnbのスタイルガイドを実装する → Done!
  • Materialデザインを実装する → Done!
  • CSSの読み込みを実装する
  • ページルーティングを実装する
  • REST通信を実装する
  • ログイン制御のフローを実装する
  • デプロイ方法を検討、実装する

次回の記事

www.hirooooo-lab.com

今までの記事