みなさんこんにちは、個別指導塾コミット塾長、AWESOME開発担当の船津です。
前回に引き続き、reduxでのアプリ制作を行っていきます。
今回はよくあるtodolistをreduxで作成します。
目次
今回のバージョンのgithubリポジトリ
一回目ではタスクの追加までを実践してみようと思います。
参考にしたのはこちらの2つ
公式サイト
http://redux.js.org/docs/basics/ExampleTodoList.html
とてもわかり易く、順番に説明されているのでとても参考になりました。
http://qiita.com/xkumiyu/items/9dfe51d2bcb3bdb06da3
reduxはハマりどころがいっぱいいっぱい
触ってみての感想ですが、reduxは習得に時間がかかります。またハマりどころもたくさんあります。今回はファイル毎にどこでハマったか、どのようにして回避したかを説明しながら進めていきます。
reduxを使うときは動作確認しながら進めていこう
いろいろな資料がありますが、作るファイルが多いので、どこから手を付けて良いのか迷いがちです。
まずはstoreの部分を作って最低限の動作を確認しよう。
例えばこんな感じです。
let store = createStore(todo) export default class App extends Component { constructor(props) { super(props) } render() { return ( <Provider store={store}> <TodoApp/> </Provider> ) } } export default class TodoApp extends Component { constructor(props) { super(props) this.state = { } } render() { const { text } = this.props; return ( <View> <Text> {text} </Text> <TouchableHighlight onPress={() => {store.dispatch({ type : 'HELLO'})}} activeOpacity={75 / 100} underlayColor={"rgb(210,210,210)"}> <Text>Press</Text> </TouchableHighlight> <View> ) } } export default function todo( state = {}, action = {}) { switch (action.type) { case 'HELLO': return { text: 'hello' } default: return state } } function mapStateToProps(state) { return { text: state.text } } export default connect(mapStateToProps)(TodoApp)
ここまで正しければ、「press」を押すと、’hello’と表示されるかと思います。
どんなアプリでもそうですが、最低限の実装をして→機能別にファイル分割の流れで進めていきましょう。
それでは作っていきましょう。
ファイル一覧
- index.ios.js アプリの登録を行います。
- components/
- TodoList.js storeから受け取ったtodosをmapで処理して個別に表示していきます。
- Todo.js Todo.jsの中で繰り返し呼ばれるコンポーネントです。todoリストの本体とも言える部分です。
- containers/
- TodoApp.js connect関数を使ってreducerのstateをreact-nativeでも使えるように関連付けます。
- app.js アプリのルートになる部分です。storeを作成します。
- reducers/
- todos.js actionに対応したstateを返します。
- index.js store作成時に呼ばれる関数をまとめて記述します。
- actions/
- index.js アクションを作成します。
それではひとつずつ見ていきましょう。
index.ios.js
- アプリを登録します。
import React, { Component } from 'react'; import App from './app/containers/app' import { AppRegistry, } from 'react-native' AppRegistry.registerComponent('TodoList', () => App);
containers/app.js
- storeを作成します。
- storeがグローバルになってますが、気にせず進めましょう。
- 今回はcreateStoreの引数に複数のreducerをまとめたものを指定します。
- といったもまだtodosしか作成されていませんが、、、
import React, { Component, } from 'react'; import { Provider } from 'react-redux'; import { createStore } from 'redux'; import reducer from '../reducers'; import TodoApp from './TodoApp'; import todo from '../reducers/todos' store = createStore(reducer) export default class App extends Component { constructor(props) { super(props) } render() { return ( <Provider store={store}> <TodoApp/> </Provider> ) } }
containers/TodoApp.js
- react-nativeとreduxのstateをconnect関数で共有できるようにします。
- 必ずconnectが期待通りの動作をしていることを確認してから進めましょう。
- ここがしっかり動作していないとこの先の全てが動きません。
import React, { Component, } from 'react'; import { connect } from 'react-redux'; import TodoList from '../components/TodoList' export default class TodoApp extends Component { static propTypes = {} static defaultProps = {} constructor(props) { super(props) this.state = { } } render() { const { todos } = this.props; return ( <TodoList todos={todos} /> ) } } function mapStateToProps(state) { return { todos: state.todos } } export default connect(mapStateToProps)(TodoApp)
components/TodoList.js
- todosオブジェクトをmapでtodoに分割して処理していきます。
- 後述のTodoを繰り返し呼んでリストのように表示させます。
- 今回は簡単のためにTextInputで入力した値を直接store.dispatchでreducerに渡しています。
- ここではちゃんとtodosオブジェクトがundefinedになっていないか確認する方が良いでしょう。
import React, { Component, } from 'react'; import { View, Text, StyleSheet, TextInput, TouchableHighlight, } from 'react-native' import { addTodo } from '../actions/index'; import Todo from './Todo'; export default class TodoList extends Component { static propTypes = {} static defaultProps = {} constructor(props) { super(props) this.state = { } } render() { const { todos } = this.props; return ( <View style={styles.container}> <View> {todos.map((todo, index) => <Todo key={index} text={todo.text} {...todo} /> )} <TextInput style={styles.input} placeholder={ 'tasks?' } placeholderTextColor={"rgba(198,198,204,1)"} onChangeText={(text) => {this.setState({text})}} onSubmitEditing={() => { store.dispatch(addTodo(this.state.text)) this.setState({text: ''}) }} value={(this.state && this.state.text) || ''} /> </View> </View> ) } } const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center', flexDirection: 'column', }, input: { height: 30, width: 100, borderWidth: 1, borderColor: "rgba(0,0,0,0.5)", borderRadius: 5, textAlign: 'center', } });
components/Todo.js
- TodoListで呼ばれる部分です。今回はTodoのテキストを表示させるだけです。
import React, { Component } from 'react'; import { View, Text, StyleSheet, TextInput, TouchableHighlight, } from 'react-native' export default class Todo extends Component { render(){ const { text } = this.props return ( <View> <Text> {text} </Text> </View> ) } }
reducers/todos.js
- actionに対してstateがどのように変化するかを記述します。
- 公式サイトのコピーなので詳しくは公式サイトをご覧ください。
export default function todo( state = {}, action = {}) { switch (action.type) { case 'ADD_TODO': return { id: action.id, text: action.text, completed: false, } case 'TOGGLE_TODO': if (state.id !== action.id){ return state } return Object.assign({},state, { completed: !state.completed }) default: return state } } export default function todos( state = [], action = {}) { switch (action.type) { case 'ADD_TODO': return [ ...state, todo(undefined, action) ] case 'TOGGLE_TODO': return state.map(t => todo(t, action) ) default: return state } }
reducers/index.js
- reducerをまとめます。今回はtodosだけですが、次回から複数になるのでcombineReducersでまとめておきます。
import { combineReducers } from 'redux'; import todos from './todos'; const reducer = combineReducers({ todos, }); export default reducer
actions/index.js
- こちらも公式のままですね。公式サイトをご覧ください。
let nextTodoId = 0; export const addTodo = (text) => { return { type: 'ADD_TODO', id: nextTodoId++, text } } export const setVisibilityFilter = (filter) => { return { type: 'SET_VISIBILITY_FILTER', filter } } export const toggleTodo = (id) => { return { type: 'TOGGLE_TODO', id } }
ここまで出来ると
こんな感じになります。
おしまい
長くなってしまいましたが、いかがだったでしょうか?
次回はdispatchをまとめたりtodoリストのタスク完了を実装していきましょう。