Edited at

ReduxのTodo SampleをMaterial UIで装飾してみた

More than 1 year has passed since last update.


背景

モダンなフロント環境フル体験しようとした

しかし、原始人な自分には早かった

小さい目標を少しずつクリアし人類へ近づこう

ほなTodoをMaterial UIで装飾しましょ


対象

React.jsのtutorialとReduxのExsampleこなしたし

Material UIでおしゃれに装飾したいんやけどどうすんねん?って人(いるのか?

Q. すでにSampleのTodoMVCがおしゃれTodoですが?

A. I don't know


成果物を出せ

こちらになります(github repositoty)


完成図

TodoSS.png


前提

React.js + Redux使える環境

package.json

{
"scripts": {
"start": "node server.js"
},
"dependencies": {
"babel-polyfill": "^6.3.14",
"react": "^0.14.7",
"react-dom": "^0.14.7",
"react-redux": "^4.1.2",
"redux": "^3.1.2"
},
"devDependencies": {
"babel-core": "^6.3.15",
"babel-loader": "^6.2.0",
"babel-preset-es2015": "^6.3.13",
"babel-preset-react": "^6.3.13",
"babel-preset-react-hmre": "^1.1.1",
"babel-register": "^6.3.13",
"cross-env": "^1.0.7",
"expect": "^1.8.0",
"express": "^4.13.3",
"jsdom": "^5.6.1",
"mocha": "^2.2.5",
"node-libs-browser": "^0.5.2",
"react-addons-test-utils": "^0.14.7",
"webpack": "^1.9.11",
"webpack-dev-middleware": "^1.2.0",
"webpack-hot-middleware": "^2.9.1"
}
}

Reduxのサンプルをそのまま利用しているので今回使ってないパッケージも含んでます


目次


  1. Todo Sampleの導入

  2. Todo Sampleの動作確認

  3. material uiの導入

  4. material uiでnavbarをつけてみる

  5. Todoをmaterial uiで装飾

  6. 動作加える


1. Todo Sampleの導入

Todo Sampleは公式のtodosを使います

以降のソースもSampleを元に手を加えていきます

redux todos(github)


2. Todo Sampleの動作確認

適当にnpm iしてstart, localhost:3000開く

動いた!Todoが動いた!


3. material uiの導入

Material UIをインストールします

$ npm install material-ui

$ npm install react-tap-event-plugin

ついでに公式サンプルそのまま利用している場合はreact関連のバージョンを上げます

Q. 無くなると言われてるreact-tap-event-pluginはまだいる?

A. 分かってないです、殴らないでください


4. material uiでnavbarをつけてみる

まずはApp.jsにてmaterial関連をインポートし、MuiThemeProviderでラップします

そして、Header(Navbar)をConponentに追加します

components/App.js

import AddTodo from '../containers/AddTodo'
import VisibleTodoList from '../containers/VisibleTodoList'

+ import injectTapEventPlugin from 'react-tap-event-plugin'
+ import getMuiTheme from 'material-ui/styles/getMuiTheme'
+ import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'
+ import Header from './Header'

+ injectTapEventPlugin();

const App = () => (
+ <MuiThemeProvider muiTheme={getMuiTheme()}>
<div>
+ <Header />
<AddTodo />
<VisibleTodoList />
<Footer />
</div>
+ </MuiThemeProvider>
)

export default App

props.childは一括りのノードって気付かずハマってた時期もありました

次にHeader.jsの実装

と言ってもmaterial uiのNavbarをコピペルだけです

components/Header.js

+ import React from 'react';
+ import AppBar from 'material-ui/AppBar';
+
+ const Header = () => (
+ <AppBar
+ title="Todo"
+ iconClassNameRight="muidocs-icon-navigation-expand-more"
+ />
+ );
+
+ export default Header;

ここまでを動作確認です

Material UIのnavbarが上部に表示されればmaterial ui導入完了です

ついでにnavbarを取り囲む余白が気になるのでbodyのmarginを0にします

index.html

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Redux Todos example</title>
+ <style>
+ body { margin: 0; }
+ </style>
</head>
<body>
<div class="todoapp" id="root">
</div>
<script src="/static/bundle.js"></script>
</body>
</html>

Material UIではRoboto Fontを採用してました

何か柔らかい感じのフォントです

さっと導入したい場合、公式のサンプルが手っ取り早いです

興味がある方はどうぞ

こちら


5. Todoをmaterial uiで装飾

まずはインプット欄をTextFieldに変更するためAddTodo.jsを変更します

containers/AddTodo.js

import React from 'react'
import { connect } from 'react-redux'
import { addTodo } from '../actions'
+ import TextField from 'material-ui/TextField';

+ const KEY_ENTER = 13

let AddTodo = ({ dispatch }) => {
let input

return (
<div>
+ <TextField
+ hintText="input text"
+ floatingLabelText="What Task?"
+ onKeyDown={e => {
+ if (e.keyCode === KEY_ENTER) {
+ dispatch(addTodo(e.target.value))
+ e.target.value = ""
+ }
+ }}
+ />
</div>
)
}
AddTodo = connect()(AddTodo)

export default AddTodo

TextFieldの値はe.target.valueで取得できます

onChangeの時、state更新すりゃいいんだよ HAHA

みたいなコメントに惑わされました

参考:material-ui/issues/2699

次、TodoListとTodoをListにします

components/TodoList.js

import React, { PropTypes } from 'react'
import Todo from './Todo'
+ import { List } from 'material-ui/List';
+ import Subheader from 'material-ui/Subheader';

const TodoList = ({ todos, onTodoClick }) => (
+ <List>
+ <Subheader>Todo List</Subheader>
{todos.map(todo =>
<Todo
key={todo.id}
{...todo}
onClick={() => onTodoClick(todo.id)}
/>
)}
+ </List>
)

TodoList.propTypes = {
todos: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.number.isRequired,
completed: PropTypes.bool.isRequired,
text: PropTypes.string.isRequired
}).isRequired).isRequired,
onTodoClick: PropTypes.func.isRequired
}

export default TodoList

components/Todo.js

import React, { PropTypes } from 'react'
+ import { ListItem } from 'material-ui/List';
+ import Divider from 'material-ui/Divider';

const Todo = ({ onClick, completed, text }) => (
+ <div>
+ <ListItem
+ onTouchTap={onClick}
+ style={{
+ textDecoration: completed ? 'line-through' : 'none'
+ }}
+ primaryText={text}
+ />
+ <Divider />
+ </div>
)

Todo.propTypes = {
onClick: PropTypes.func.isRequired,
completed: PropTypes.bool.isRequired,
text: PropTypes.string.isRequired
}

export default Todo

最後にHeaderの三クリックでIcon Menu出してみます

components/Header.js

import React from 'react'
import AppBar from 'material-ui/AppBar'
+ import IconButton from 'material-ui/IconButton';
+ import IconMenu from 'material-ui/IconMenu';
+ import MenuItem from 'material-ui/MenuItem';
+ import MenuIcon from 'material-ui/svg-icons/navigation/menu';
import FilterLink from '../containers/FilterLink'
+
+ const HeaderIcon = () => (
+ <IconMenu
+ iconButtonElement={<IconButton><MenuIcon color={'white'} /></IconButton>}
+ anchorOrigin={{horizontal: 'left', vertical: 'top'}}
+ targetOrigin={{horizontal: 'left', vertical: 'top'}}
+ >
+ <MenuItem>
+ <FilterLink filter="SHOW_ALL">
+ All
+ </FilterLink>
+ </MenuItem>
+ <MenuItem>
+ <FilterLink filter="SHOW_ACTIVE">
+ Active
+ </FilterLink>
+ </MenuItem>
+ <MenuItem>
+ <FilterLink filter="SHOW_COMPLETED">
+ Completed
+ </FilterLink>
+ </MenuItem>
+ </IconMenu>
+ );

const Header = () => (
<AppBar
title="Todo"
+ iconElementLeft={<HeaderIcon />}
/>
)

export default Header

MenuItemは難しく考えずFilterLinkでくくってやればいけました

ついでにaタグである必要がないので、Link.jsのaタグをpタグ変更しました

クリックした時微妙に挙動がおかしいですが、大丈夫です

以上で完成です

お疲れ様でした!


感想

ある程度機能が完成されていれば、material uiへの差し替え、もといViewの差し替えは簡単に出来るなーと

これがコンポーネントか!というのが理解できました

ただ、今回いじったのはView関連のみでした

思ってたより簡単にできたのは嬉しかったですが...

Action CreaterやReducerを一から書くということをやってない!

なので、その辺の理解がまだまだです

予定では、自分こんだけフロントできます!

仕事ください!っていうアピールする予定でしたが、いつになるやら...

フロント楽しいですし、がっつりフロント本職にしたいやりたいです、以上!