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

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

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

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

参考にしたのはこちらの2つ
公式サイト

とてもわかり易く、順番に説明されているのでとても参考になりました。

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
  }
}

ここまで出来ると

こんな感じになります。

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

おしまい

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

PR

Instagram Feed

Load More
Something is wrong. Response takes too long or there is JS error. Press Ctrl+Shift+J or Cmd+Shift+J on a Mac.

Instagram

専務取締役

funatsukeisuke

WEBと教育を組み合わせて何かおもしろいことをやってやろうと画策しています。AWESOMEでは開発を担当、個別指導塾コミットでは塾長と2足の草鞋を履きながら日々勉強しています。

他の投稿を見る →