20151210_Material_UI_logo

この記事はQiita React.js Advent Calendar 2015の11日目に投稿させて頂いた記事です。

背景

Material UIというプロジェクトをご存知だろうか?これはGoogle Material Designが提供しているUIパーツをReactのコンポーネントとして配布するプロジェクトである。自分はフロントエンドエンジニアじゃないし、デザインに時間かけたくないからとりあえずBootstrapでも適用しておくかみたいなケースは結構あると思う。僕もRailsで書いたAPIサーバの管理画面を開発した時、最初はBootstrapを使おうと思っていた。でも、Bootstrapのデザインも飽きてきたし他に気軽に使えるものはないかな、と探していた時に見つけたのがMaterial UI。Reactも書けるようになってきたので使ってみることにした。

成果物

React + Redux + Material UIのboilerplateを作った。理由は2015年9月頃はReactとReduxとMaterial UIのバージョンの制限が厳しく少しバージョンが変わるだけで利用できなかったり、Webpack, ESLint、Redux Routerなどの設定を毎回するのが面倒だったから。このboilerplateでちょっとしたアプリを作る時は以下に置きますので使って下さい。

Material UIで遊ぶ

Materia UIのウェブページにアクセスして、COMPONENTSにアクセスすると利用可能なMeterial Designなコンポーネントが表示される。コンポーネントの挙動を確認するために実際に手元でコードをいじっても良いが、以下のようにサイト内でインタラクティブに各コンポーネントの動作を確認できるので、まずは遊んで見ることをお薦めする。

material_ui_tab material_ui_progress

Material UIを使ってみる

実際にプロジェクトの開始からステップバイステップでReactアプリにMaterial UIを組み込み以下のような構成のReactアプリを作ってみる。[1]

<App>
  <Header />
  <MainSection/>
</App>

また、最終的なフォルダ構成はこうなる。

.
├── .eslintrc
├── components
│   ├── Header.jsx
│   └── MainSection.jsx
├── containers
│   └── App.jsx
├── package.json
├── src
│   ├── index.html
│   ├── index.jsx
│   ├── main.css
│   └── material_ui_raw_theme_file.jsx
├── static
│   ├── bundle.js
│   ├── index.html
│   └── main.css
└── webpack.config.js

アプリ作成時のnodeのバージョンは3.3.6。

プロジェクトの準備

$ mkdir material_ui_sample
$ cd material_ui_sample
$ npm init
$ vim package.json
$ npm install
$ touch webpack.config.js
$ touch .eslinrc

ここで、Javascriptのモジュール管理はnpmで行うためvim package.jsonの時に以下の内容を記述する。

{
  "name": "react-redux-material_ui-boilerplate",
  "version": "1.0.0",
  "description": "A boilerplate for React + Redux + Material UI + ES6 syntax application",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "webpack-dev-server --hot --inline --progress --colors",
    "build": "webpack --progress --colors"
  },
  "repository": {
    "type": "git",
    "url": ""
  },
  "keywords": [
    "React",
    "webpack",
    "Redux",
    "Bable",
    "ES6"
  ],
  "author": "https://github.com/takanabe",
  "license": "MIT",
  "bugs": {
    "url": ""
  },
  "homepage": "",
  "devDependencies": {
    "babel-core": "^5.8.25",
    "babel-eslint": "^4.1.3",
    "babel-loader": "^5.3.2",
    "babel-runtime": "^5.8.25",
    "css-loader": "^0.19.0",
    "eslint": "^1.6.0",
    "eslint-loader": "^1.1.0",
    "eslint-plugin-react": "^3.5.1",
    "fbjs": "^0.2.1",
    "file-loader": "^0.8.4",
    "style-loader": "^0.12.4",
    "webpack": "^1.12.2",
    "webpack-dev-server": "^1.12.0",
    "webpack-hot-middleware": "^2.2.0"
  },
  "dependencies": {
    "classnames": "^2.1.2",
    "fbjs": "^0.3.2",
    "material-ui": "^0.13.0",
    "react": "^0.14.0",
    "react-addons-create-fragment": "^0.14.0",
    "react-addons-pure-render-mixin": "^0.14.0",
    "react-addons-transition-group": "^0.14.0",
    "react-addons-update": "^0.14.0",
    "react-dom": "^0.14.0",
    "react-hot-loader": "^1.3.0",
    "react-redux": "^3.1.0",
    "react-tap-event-plugin": "^0.2.0",
    "redux": "^3.0.2"
  }
}

また、Javascriptのビルド、ES6からES5へのトランスパイルはWebpack + babelを利用するものとして以下の設定を記載する。

module.exports = {
  context: __dirname + "/src",
  entry: {
    jsx: "./index.jsx",
    css: "./main.css",
    html: "./index.html",
  },

  output: {
    path: __dirname + "/static",
    filename: "bundle.js",
  },
  module: {
    preLoaders: [
        //Eslint loader
      { test: /\.(js|jsx)$/, exclude: /node_modules/, loader: "eslint-loader"},
    ],
    loaders: [
      { test: /\.html$/, loader: "file?name=[name].[ext]" },
      { test: /\.css$/, loader: "file?name=[name].[ext]" },
      { test: /\.(js|jsx)$/, exclude: /node_modules/, loaders: ["react-hot","babel-loader?stage=0&optional=runtime"]},
    ],
  },
  resolve: {
    extensions: ['', '.js', '.jsx']
  },
  eslint: {
    configFile: './.eslintrc'
  },
};

この設定ファイルでsrcディレクトリにビルド前のファイルを、staticディレクトリにビルド後のファイルを置くようにしている。また、JavascriptのLintingにはESLintを使うので以下を記載する。

{
  "env": {
    "es6": true,
    "browser": true,
    "node": true
  },
  "rules": {
    "curly": 0,
    "comma-dangle": [2, "never"],
    "comma-spacing": 0,
    "eqeqeq": [2, "allow-null"],
    "key-spacing": 0,
    "no-underscore-dangle": 0,
    "no-unused-expressions": 0,
    "no-shadow": 0,
    "no-shadow-restricted-names": 0,
    "no-extend-native": 0,
    "no-var": 2,
    "new-cap": 0,
    "quotes": 0,
    "semi-spacing": 0,
    "space-unary-ops": 0,
    "space-infix-ops": 0,
    "consistent-return": 0,
    "strict": 0
  },
  "parser": "babel-eslint",
  "plugins": [
    "react"
  ],
  "ecmaFeatures": {
    "arrowFunctions": true,
    "jsx": true
  }
}

最後にビルドのベースとなるファイルを作成する。

$ mkdir src
$ echo "index.html\nindex.jsx\nmain.css" | xargs -I% touch src/%

ここで一度ビルドが通ることを確認する。

$ npm run build

コマンド実行後にstaticディレクトリが出来ており、その中にbundle.jsindex.htmlmain.cssがあれば準備完了。

Appコンポーネントの準備をする

src/index.jsxを以下のように編集する。この時、Material UIで提供しているコンポーネントでタップイベントをキャッチするのにreact-tap-event-pluginが必要なことがあるので忘れずにimportする。これは、reactv1.0がリリースされるタイミングで必要なくなるとのこと。

import React from "react";
import ReactDOM from "react-dom";
import injectTapEventPlugin from "react-tap-event-plugin";

import App from '../containers/App';

//Needed for React Developer Tools
window.React = React;

//Needed for onTouchTap
//Can go away when react 1.0 release
//Check this repo:
//https://github.com/zilverline/react-tap-event-plugin
injectTapEventPlugin();

ReactDOM.render(
  <App />,
  document.getElementById("root")
);

このsrc/index.jsxは`src/index.htmlから読み込むようにする。

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Boilerplate</title>
    <link rel="stylesheet" type="text/css" href="main.css">
  </head>
  <body>
    <div id="root"></div>
    <!-- This script adds the Roboto font to our project. For more detail go to this site:  http://www.google.com/fonts#UsePlace:use/Collection:Roboto:400,300,500 -->
    <script>
      var WebFontConfig = {
        google: { families: [ 'Roboto:400,300,500:latin' ] }
      };
      (function() {
        var wf = document.createElement('script');
        wf.src = ('https:' == document.location.protocol ? 'https' : 'http') +
          '://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js';
        wf.type = 'text/javascript';
        wf.async = 'true';
        var s = document.getElementsByTagName('script')[0];
        s.parentNode.insertBefore(wf, s);
      })();
    </script>
    <script src="./bundle.js"></script>
  </body>
</html>

また、CSS周りを設定するときはsrc/main.cssを利用する。

html {
  font-family: 'Roboto', sans-serif;
}

body {
  font-size: 13px;
  line-height: 20px;
  margin: 0px;
}

続いて、Reactコンポーネントを作成する。

$ mkdir containers
$ touch containers/App.jsx

ここで、HeaderとMainSectionは子コンポーネントとするためApp.jsxは親コンポーネントを以下のように編集する。

import { connect } from 'react-redux';
import Header from '../components/Header';
import MainSection from '../components/MainSection';

class App extends Component {
  render() {
    return (
      <div>
        <Header />
        <MainSection />
      </div>
    );
  }
}

export default App;

Material UIを子コンポーネントで使う

お待たせしました、ようやくMaterial UIを使います!ここからがこの記事のメインです。

$ mkdir components
$ echo "Header.jsx\nMainSection.jsx\n" | xargs -I% touch %

Header.jsxではMaterial UIのAppBarコンポーネントを使ってみる。Material UIのコンポーネントを使うときはimport文 + {AppBar}のようにして使いたいコンポーネントを指定する。また、render関数内では、Material UIの各コンポーネントのサンプルコードを真似てコンポーネントを使えば良い。

import React, { PropTypes, Component } from 'react';

import mui, {AppBar} from 'material-ui';
const ThemeManager = require('material-ui/lib/styles/theme-manager');
import MyRawTheme from '../src/material_ui_raw_theme_file'

class Header extends Component {
  static get childContextTypes() {
    return { muiTheme: React.PropTypes.object };
  }

  getChildContext(){
    return {  muiTheme: ThemeManager.getMuiTheme(MyRawTheme)};
  }

  render() {
    return (
      <header className="header">
        <h1>AppBar Component</h1>
        <AppBar title="React + Redux + Material UI Boilerplate" />
      </header>
    );
  }
}

export default Header;

この時、src/material_ui_raw_theme_file.jsxを以下のように用意すればMaterial UIの細かい設定も変更出来る。

let Colors = require('material-ui/lib/styles/colors');
let ColorManipulator = require('material-ui/lib/utils/color-manipulator');
let Spacing = require('material-ui/lib/styles/spacing');

module.exports = {
  spacing: Spacing,
  fontFamily: 'Roboto, sans-serif',
  palette: {
    primary1Color: Colors.cyan500,
    primary2Color: Colors.cyan700,
    primary3Color: Colors.lightBlack,
    accent1Color: Colors.pinkA200,
    accent2Color: Colors.grey100,
    accent3Color: Colors.grey500,
    textColor: Colors.darkBlack,
    alternateTextColor: Colors.white,
    canvasColor: Colors.white,
    borderColor: Colors.grey300,
    disabledColor: ColorManipulator.fade(Colors.darkBlack, 0.3)
  }
};

同様に、MainSection.jsxも編集する。MainSection.jsxではProgress,Tab,DataPikcerコンポーネントを使ってみる。それぞれimport文で呼び出してrender関数内で使うだけである。

import React, { Component, PropTypes } from 'react';
import mui, {CircularProgress,
             Tabs,
             Tab,
             DatePicker
            } from 'material-ui';

class MainSection extends Component {
  render() {
    return (
      <div>
        <h1>Progress Component</h1>
        <CircularProgress mode="indeterminate" size={1.5} />
        <CircularProgress mode="indeterminate" color={"red"} size={2} />
        <br/>

        <h1>Tab Component</h1>
        <Tabs>
          <Tab label="Tab One" value="0" />
          <Tab label="Tab Two" value="1" />
          <Tab label="Tab Three" value="2" />
        </Tabs>
        <br/>

        <h1>DatePicker Component</h1>
        <DatePicker hintText="Portrait Dialog" />
        <br/>
      </div>
    );
  }
}

export default MainSection;

作ったReactアプリにアクセスしてMaterial UIコンポーネントを確認する。nodeの開発用サーバを使って、作ったReactアプリにアクセスするには以下を実行後、ブラウザを開きlocalhost:8080にアクセスする。

$ npm start

すると、このようにMaterial UIのコンポーネントが並んで表示される。

20151211_sample_app_for_material_ui

便利だ!

使ってみた感想

別件で、Material UIを使ってこんな感じの管理画面アプリを作った。こいつがReact + Redux + Material UIな構成で初めて作ったフロントエンドアプリケーションになった。

demo_material_ui

ご覧の通りカッコいいUIが実現できる。ただし、Material UIコンポーネントに細かい挙動を追加したりする場合、material-ui/docs/src/app/components/pages/components at master · callemall/material-ui · GitHub を見たり、コンポーネントごとのコードを読んだりする必要があり結構苦労した。そういう所で時間を無駄にしたくない人は枯れているBootStrapを使った方がいいと思う。

参考

[1] 若干冗長だが自分の備忘録とアウトプットを兼ねている。

入門 React ―コンポーネントベースのWebフロントエンド開発
  • Author: Frankie Bagnardi
  • Manufacturer: オライリージャパン
  • Publish date: 2015-04-03