LoginSignup
19

More than 5 years have passed since last update.

世界一シンプルなReact-native + Reduxチュートリアル2

Last updated at Posted at 2018-04-16

alt

シンプルなTodoアプリのstate管理をreduxへと移します。
reduxではまりがちなArrayを使ったいいサンプルだと思います。
なぜならreduxではmutativeなメソッドであるpush, spliceなどの配列操作のメソッドは非推奨です。
ES6のスプレッド演算子かsliceメソッドを使いましょう。

それでは以下のtodoアプリをgit cloneしてください。
https://github.com/brunolsantos/React-Native-ToDoApp
react-native run-iosして動くことを確認しましょう。
以下のような画面が現れます。
スクリーンショット 2018-04-17 午前0.01.45.png

さあそれではこのシンプルなアプリにreduxを入れていきましょう。
まずはrootフォルダで

yarn add redux react-redux

reduxとreactを一緒に使うにはreact-reduxが必要です。

必要なフォルダ、ファイルを作りましょう。
以下のようにフォルダとファイルを作り直してください。
スクリーンショット 2018-04-15 午後9.58.38.png

1. rootReducerを作ります。

rootReducerはreduxのAPIのcombineReducersで全てのreducerを一つにまとめ扱いやすくしたreducerです。


// src/rootReducer
import { combineReducers } from "redux";
import HomeReducer from "./Home/reducer";

export default combineReducers({
  home: HomeReducer
  // mapStateToPropsでstate.home.fooなどと参照できる
});

2. storeを作ります。

state専用データベースのようなものです。実態は巨大なjsonです。一度、console.log(store)して確かめてみましょう。ここにstateを集約することでスパゲッティを防いでいます。

// src/store.js
import { createStore } from "redux";
import rootReducer from "./rootReducer";

export const store = createStore(rootReducer);

3. Providerで配下のコンポーネントにstoreを渡します。

JSXで囲んであげましょう。


// src/App.js
import React, { Component } from "react";
import { Provider } from "react-redux";
import { store } from "./store";
import Home from "./Home/homeContainer";

const App = () => (
  <Provider store={store}>
    <Home />
  </Provider>
);

export default App;

以上で、どのアプリでも必要なreduxのセットアップを書き終えました。

次にactionを作っていきましょう。actionとはreducerで扱うデータをjsonにしたものです。reducerは純粋な関数、actionは関数に渡すオブジェクト/引数と考えてokです。なお、actionはreduxの機能ではなく、純粋なjsです。
const ADD_NOTE = "ADD_NOTE"の部分はredux的に必要です。
諦めて書きましょう。

4. actionを作成する。

// src/Home/actions.js
export const ADD_NOTE = "ADD_NOTE";
export const DELETE_NOTE = "DELETE_NOTE";

export const addNote = payload => ({
  type: "ADD_NOTE",
  payload: { noteText: payload }
});

export const deleteNote = payload => ({
  type: "DELETE_NOTE",
  payload
});

5. reducerを作成する。

ここで作成したreducerをrootReducerに食わせます。reducerは純粋な関数です。
reducerはstateを加工してdispatchでstoreに保存します。
switch文でactionの名前ごとに処理を分けます。
reducer自体に名前は必要ないので省略していますが、(export defaultされるものに名前は必要ない)名前をつけることも出来ます。


// src/Home/reducer.js
const INITIAL_STATE = {
  noteArray: []
};

export default (state = INITIAL_STATE, action) => {
  switch (action.type) {
    case "ADD_NOTE":
      return {
        noteArray: [...state.noteArray, action.payload]
      };
    case "DELETE_NOTE":
      return {
        ...state,
        noteArray: [...state.noteArray, action.payload]
      };
    default:
      return state;
  }
};

7. containerを作成する。containerとはreduxからstateを受け取るコンポーネントです。connect関数を利用してstoreからstateを受け取ります。stateを受け取らないコンポーネントはcomponentフォルダに置くのが一般的です。

簡潔に言うと、
mapStateToProps = グローバルstate
mapDispatchToProps = dispatchする関数
を表します。。

// src/Home/homeContainer.js
import React, { Component } from "react";
import {
  View,
  Text,
  StyleSheet,
  TextInput,
  ScrollView,
  TouchableOpacity
} from "react-native";
import { connect } from "react-redux";
import { addNote, deleteNote } from "./actions";

import Note from "./components/note";

class Home extends Component {
  // constructorはthis.stateを使う時のみ必要。reactの機能ではなく、jsの機能のなので注意。
  constructor(props) {
    super(props);
    this.state = {
      noteText: ""
    };
  }

  // renderとreturnの間には、render以下で使う変数を定義できる。ここではJSXをnotesという変数に入れている。
  // arrayの中には、keyを保存せず、子コンポーネントに渡すときにゼロからmapでキーを生成している。
  render() {
    let notes = this.props.noteArray.map((val, key) => {
      return (
        <Note
          key={key}
          keyval={key}
          val={val}
          deleteMethod={() => this.deleteNote(key)}
        />
      );
    });
    return (
      <View style={styles.container}>
        <View style={styles.header}>
          <Text style={styles.headerText}>- NOTER -</Text>
        </View>
        <ScrollView style={styles.scrollContainer}>{notes}</ScrollView>

        <View style={styles.footer}>
          <TextInput
            style={styles.textInput}
            placeholder=">note"
            onChangeText={noteText => this.setState({ noteText })}
            value={this.state.noteText}
            placeholderTextColor="white"
            underlineColorAndroid="transparent"
          />
        </View>
        <TouchableOpacity
          onPress={this.addNote.bind(this)}
          style={styles.addButton}
        >
          <Text style={styles.addButtonText}>+</Text>
        </TouchableOpacity>
      </View>
    );
  }

  addNote() {
    if (this.state.noteText) {
      this.props.onAddNote(this.state.noteText);
      this.setState({ noteText: "" });
    }
  }

  deleteNote(key) {
    this.state.noteArray.splice(key, 1);
    this.setState({ noteArray: this.state.noteArray });
  }
}

const mapStateToProps = state => {
  // stateとはstoreのこと。巨大なjsonで全てのstateを管理する
  console.log(state)
  return {
    noteArray: state.home.noteArray
  };
};

const mapDispatchToProps = dispatch => {
  // dispatchも確認してみよう。
  console.log(dispatch)
  return {
    onAddNote: text => dispatch(addNote(text))
  };
};

export default connect(mapStateToProps, mapDispatchToProps)(Home);

const styles = StyleSheet.create({
  container: {
    flex: 1
  },
  header: {
    backgroundColor: "#E91E63",
    alignItems: "center",
    justifyContent: "center",
    borderBottomWidth: 10,
    borderBottomColor: "#ddd"
  },
  headerText: {
    color: "white",
    fontSize: 18,
    padding: 26
  },
  scrollContainer: {
    flex: 1,
    marginBottom: 100
  },
  footer: {
    position: "absolute",
    bottom: 0,
    left: 0,
    right: 0,
    zIndex: 10
  },
  textInput: {
    alignSelf: "stretch",
    color: "#fff",
    padding: 20,
    backgroundColor: "#252525",
    borderTopWidth: 2,
    borderTopColor: "#ededed"
  },
  addButton: {
    position: "absolute",
    zIndex: 11,
    right: 20,
    bottom: 90,
    backgroundColor: "#E91E63",
    width: 70,
    height: 70,
    borderRadius: 35,
    alignItems: "center",
    justifyContent: "center",
    elevation: 8
  },
  addButtonText: {
    color: "#fff",
    fontSize: 24
  }
});

9. note.jsをcomponentsの中に移動する。


// src/components/nots.js
import React, { Component } from "react";
import { View, Text, StyleSheet, TouchableOpacity } from "react-native";

export default class Note extends Component {
  render() {
    return (
      <View key={this.props.keyval} style={styles.note}>
        <Text style={styles.noteText}>{this.props.val.noteText}</Text>

        <TouchableOpacity
          onPress={this.props.deleteMethod}
          style={styles.noteDelete}
        >
          <Text style={styles.noteDeleteText}>D</Text>
        </TouchableOpacity>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  note: {
    position: "relative",
    padding: 20,
    paddingRight: 100,
    borderBottomWidth: 2,
    borderBottomColor: "#ededed"
  },
  noteText: {
    paddingLeft: 20,
    borderLeftWidth: 10,
    borderLeftColor: "#E91E63"
  },
  noteDelete: {
    position: "absolute",
    justifyContent: "center",
    alignItems: "center",
    backgroundColor: "#2980b9",
    padding: 10,
    top: 10,
    bottom: 10,
    right: 10
  },
  noteDeleteText: {
    color: "white"
  }
});

ところで、このメモアプリ一つ欠点がある。
それはアプリを閉じるとメモが消えてしまうこと笑
そこでredux-persistを使いstoreを永続化(スマホのデータベースに保存)することにより解決する。
さあ次のチュートリアルに進もう。
あと少しで君もRNマスターだ。
世界一シンプルなReact-native + Reduxチュートリアル3

最後に

4つもファイルが増えました。めんどくさいですね。
本当にredux必要ですか?
unstatedなら30分で基本的な使い方マスターできますよ。
ただ、大規模になればなるほど、疎結合であり、スパゲッティになりにくいreduxの強みが生きてきます。
とてもよくスケールするので、他人の書いたコードを気にせずに自分のコードを書けるのでストレスが貯まりません。

reduxのことは嫌いになってもReactNativeのことは嫌いにならないでください。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19