メモです。
基本的な使い方を試してみた。
Redux の basic チュートリアル にそって作った Todo アプリをベースに、Material-UI を適用してみる。
https://github.com/zaki-yama/redux-todo-app/pull/2
(Material-UI 適用前のコードには base というタグをつけてます)
なお、公式 example リポジトリも存在するよう。
https://github.com/callemall/material-ui-webpack-example
インストール
$ npm install --save material-ui
でインストール。
導入手順
react-tap-event-plugin をインストールする
http://www.material-ui.com/#/get-started/installation によると、react-tap-event-plugin というパッケージが必要らしい。
Material-UI と同様に npm でインストールする。
$ npm install --save react-tap-event-plugin
このパッケージにより、Material-UI のすべてのコンポーネントは onTouchTap というイベントハンドラが使えるようになる。
マウスクリックやスマホ/タブレットのタッチ・タップイベントを共通のイベントハンドラで処理できるようになる、のかな?
また、上記ページには
This dependency is temporary and will go away once the official React version is released.
と書いており、試したときの React のバージョンは 15.3.2 だったので
メジャーバージョンが 1 以上だからこのプラグイン無しでも動くということかな?と思ったけど、結局インストールしないとうまく動かなかった。
react-tap-event-plugin をインストールした後は、以下のコードをアプリの先頭に記述する必要がある。
import injectTapEventPlugin from 'react-tap-event-plugin';
injectTapEventPlugin();
よって Redux の場合、アプリのエントリーポイント(src/index.js)に以下のように追記するはず。
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
+import injectTapEventPlugin from 'react-tap-event-plugin';
import todoApp from './reducers';
import App from './containers/App';
+// Needed for onTouchTap
+// http://stackoverflow.com/a/34015469/988941
+injectTapEventPlugin();
+
const store = createStore(todoApp);
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'),
);
Roboto フォントを適用する
またまた
http://www.material-ui.com/#/get-started/installation#roboto-font
を読むと、
Material-UI was designed with the Roboto font in mind
とあるので、Roboto フォント を読み込むようにする。
html ファイルに
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500">
と書き、CSS でも
html {
font-family: 'Roboto', sans-serif;
}
とする。
確認したところ html 側の記述だけでも Material-UI コンポーネントに Roboto フォントが適用されるが、
アプリ全体のフォントを統一するため CSS でも指定することにした。
なお、話は逸れるが webpack で CSS を扱う方法はいくつか考えられる。
├── dist .................. ビルド後のファイルを置く場所
├── src
│ ├── actions
│ ├── components
│ ├── constants
│ ├── containers
│ ├── index.html
│ ├── index.js
│ ├── main.css
│ └── reducers
└── webpack.config.js
というディレクトリ構成の場合、
1) CSS は CSS のままで html から読み込む場合
html 側は
<link rel="stylesheet" type="text/css" href="main.css">
とし、webpack.config.js では file-loader を使って dist ディレクトリに CSS ファイルもコピーされるようにする。
context: __dirname + '/src',
entry: {
javascript: './index.js',
- html: './index.html'
+ html: './index.html',
+ css: './main.css',
},
output: {
path: __dirname + '/dist',
@@ -27,6 +28,10 @@ module.exports = {
{
test: /\.html$/,
loader: 'file?name=[path][name].[ext]'
+ },
+ {
+ test: /\.css$/,
+ loader: 'file?name=[path][name].[ext]'
}
]
}
2) JavaScript にバンドルする場合
CSS は JavaScript 内で import してしまうという場合、html の代わりに src/index.js で
import './main.css';
とし、webpack.config.js では css-loader と style-loader を使うようにする。
{
test: /\.html$/,
loader: 'file?name=[path][name].[ext]'
- }
+ },
+ {
+ test: /\.css$/,
+ loader: 'style!css',
+ },
]
どちらでもいいですが、2) の方が後々 SCSS とかにも応用できるので良さげ。
<MuiThemeProvider>
でアプリケーション全体をラップする
コンポーネントを使う前の最後の準備として、アプリケーション全体を <MuiThemeProvider>
というコンポーネントでラップする。
エントリーポイントになっている src/index.js でやってもいいと思うし、コンポーネントの root になっている containers/App.js でもいいはず。
containers/App.js
import React from 'react';
+import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
+
import AddTodo from './AddTodo';
import VisibleTodoList from './VisibleTodoList';
import FilterLinkList from '../components/FilterLinkList';
export default class App extends React.Component {
render() {
return (
- <div>
- <AddTodo />
- <VisibleTodoList />
- <FilterLinkList />
- </div>
+ <MuiThemeProvider>
+ <div>
+ <AddTodo />
+ <VisibleTodoList />
+ <FilterLinkList />
+ </div>
+ </MuiThemeProvider>
);
}
}
Material-UI コンポーネントを使う
ここまでできるとようやく Material-UI のコンポーネントを使うことができる。
今回は簡単な例で、Todo の入力フォームとボタンをそれぞれ TextField と RaisedButton に置き換えてみる。
元のコードは以下。
components/AddTodoForm.js
import React from 'react';
export default class AddTodoForm extends React.Component {
handleSubmit(e) {
e.preventDefault();
const node = this.refs.input;
const text = node.value.trim();
if (!text) {
return;
}
this.props.onSubmit(text);
node.value = '';
}
render() {
return (
<div>
<form onSubmit={(e) => this.handleSubmit(e)}>
<input ref="input" />
<button type="submit">
Add Todo
</button>
</form>
</div>
);
}
}
これを以下のようにする。
import React from 'react';
import RaisedButton from 'material-ui/RaisedButton';
import TextField from 'material-ui/TextField';
export default class AddTodoForm extends React.Component {
constructor(props) {
super(props);
this.state = {
input: '',
};
}
handleChange(e) {
this.setState({
input: e.target.value,
});
}
handleSubmit(e) {
e.preventDefault();
const text = this.state.input.trim();
if (!text) {
return;
}
this.props.onSubmit(text);
this.setState({
input: '',
});
}
render() {
return (
<div>
<form onSubmit={(e) => this.handleSubmit(e)}>
<TextField
id="todo-title"
value={this.state.input}
onChange={(e) => this.handleChange(e)}
/>
<RaisedButton
label="Add Todo"
onTouchTap={(e) => this.handleSubmit(e)}
/>
</form>
</div>
);
}
}
フォームに入力されたテキストを管理するのに ref を使っていたのを local state で管理するようになったため全体のコード量が増えた。
ref のまま Material-UI のコンポーネントを管理することもできるみたいなんだけど、ドキュメントに載っていない方法なのでやめた。
あとで別の記事にでもメモする。
この変更によってアプリの UI は以下のように変更される。
before
after
ちょっとだけそれっぽい UI になった。
おわりに
ということで React/Redux なアプリに Material-UI を導入することができた。
意外と事前準備としてやることが多く、またそれがドキュメントからは微妙にわかりづらかったりして思った以上に手こずった。
ボタンを使うと Unknown prop `onTouchTap` on <button> tag.
が出る
→react-tap-event-plugin が入っていない。
リファレンス