ローディング中

react-nativeでiOSアプリ開発者に! 第8回 redux(todoappその1)

みなさんこんにちは、個別指導塾コミット塾長、AWESOME開発担当の船津です。
前回に引き続き、reduxでのアプリ制作を行っていきます。
今回はよくあるtodolistをreduxで作成します。

目次

今回のバージョンのgithubリポジトリ

https://github.com/keisukefunatsu/app-education/tree/2a8e0fca939489207f5412f6a43dd7faf71f0f30/TodoList

一回目ではタスクの追加までを実践してみようと思います。

参考にしたのはこちらの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
  }
}

ここまで出来ると

こんな感じになります。

Screen Shot 2016-08-15 at 18.53.24

おしまい

長くなってしまいましたが、いかがだったでしょうか?
次回はdispatchをまとめたりtodoリストのタスク完了を実装していきましょう。

ホームページ・デザイン制作のご相談・ご依頼は

079-451-5881

68ED4C65-BA1E-46D0-A7AC-B9869923C696 Created with sketchtool. A07C553F-CF16-4B7D-94FE-D36AE0A6297D Created with sketchtool. 37B7D8F4-7A18-4725-9EC6-595ABDF69D64 Created with sketchtool. 95DFB680-1EDF-4199-BC4A-BB6752EBA23C Created with sketchtool. 02DDF754-E1DB-4EF2-B6BB-C884722938C4 Created with sketchtool. NEW! 1A95E369-6469-4C46-B38F-F83410B928B0 Created with sketchtool. Group 5Group 2Group 4Group 3E4FC8CF2-721D-4699-8162-18E886C432C1 Created with sketchtool.