43
41

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

React Nativeハンズオン〜initからtodoアプリ実装まで〜

Posted at

React Nativeのハンズオン用に react-native init <プロジェクト名> してからTodoアプリを作るまでの流れを記事にします。store管理にMobXを使う予定ですが、他に使いたいものがある場合は適宜読み替えてください。また、限られた時間で、説明しづらいところはぼかしています。詳しく知りたい方はドキュメントなどを参考にしてください。

ハンズオンでできる予定のコードは下記に公開しています。
https://github.com/OshiroSeiya/AwesomeProject

対象者

  • ワタシハジャバスクリプトチョットデキル。って方
  • React Nativeのざっくりとした使い方知りたいって方
  • Mac使ってる人(ios版で書くのでandroidがやりたい人は読み替えてください)

必要となる周辺知識

最低限カッコ書きの部分のイメージがついてるとわかりやすいと思います

  • Babel(いい感じに変換してくれる)
  • React(いい感じに描画してくれる)
  • MobX(state管理をReactから奪う)

環境構築

node, yarn, watchman, react-native-cliのインストール
(yarnは好みなので入れてますがnpmでも問題ないです)

$ brew install node
$ brew install yarn
$ brew install watchman
$ npm install -g react-native-cli

上記のツールがインストールできているか確認しましょう

$ node -v
v8.2.1
$ yarn --version
0.27.5
$ watchman -v
4.7.0
$ react-native -v
react-native-cli: 2.0.1
react-native: n/a - not inside a React Native project directory

Xcodeインストールページ

Xcodeはインストール後に一度起動して利用規約的なものを許可する必要があります。
※僕の手元でXcodeのインストールに30分ぐらいかかりました。

プロジェクト作成

今回は公式のサンプルに合わせて、AwesomeProjectって名前で作ることにします。

$ react-native init AwesomeProject

ディレクトリ移動して実行できるか確認します。
うまく行けば、iOSシミュレータが起動します。

$ cd AwesomeProject
$ react-native run-ios

初回ビルドなのでそこそこ時間かかります。

Welcome to React Native! がシミュレータに表示されていればokです

うまくいかない場合は

  • 環境構築時にすべてインストールできているかもう一度確認しましょう
  • Xcodeの利用規約的なものを許可されてるか起動して確認しましょう(たまにアップデート来てて昔okしたのにまた聞かれてることがあります)

ハンズオン開始

ディレクトリ構造の変更

初期のディレクトリ構造は下記です。

$ tree -L 1
.
├── __tests__        // テストコード入れる場所(今回は使わない)
├── android          // Androidのプロジェクト(基本的ネイティブの機能を使うとき以外触らない)
├── app.json         // プロジェクト名など
├── index.android.js // Androidのエントリーファイル
├── index.ios.js     // Iosのエントリーファイル
├── ios              // Iosのプロジェクト(基本ネイティブの機能を使うとき以外触らない)
├── node_modules     // パッケージたち(インストールしたら入る)
├── package.json     // パッケージ情報記載
└── yarn.lock        // パッケージのバージョン情報記載(自動生成)

androidとiosで共通のコードをまとめたいので src ディレクトリを生成して main.js に共通処理をまとめて、エントリーファイルから呼び出せるように変更します。

$ tree -L 1
.
├── __tests__
├── android
├── app.json
├── index.android.js
├── index.ios.js
├── ios
├── node_modules
├── package.json
├── src               // これを生成した!
└── yarn.lock

main.jsindex.ios.jsをコピーして配置してください。

サンプルは下記です。

Welcome to React Native! という文字列を変更してシミュレーターに反映されればokです。

パッケージの追加

MobXが使いたいやら、かんたんに見た目よくしたいやら、オブジェクトのスプレッド演算子が使いたいやら、デコレーターが使いたいやらあるので下記のパッケージを追加します。

$ yarn add babel-preset-react-native-stage-0 babel-plugin-transform-decorators-legacy -D
$ yarn add mobx mobx-react react-native-vector-icons react-native-elements

デコレーター使いたい、スプレッド演算子使いたい、その他諸々の理由で下記に変更します。

.babelrc
{
-  "presets": ["react-native"]
+  "presets": [
+    "react-native",
+    "react-native-stage-0"
+  ],
+  "plugins": [
+    "transform-decorators-legacy"
+  ]
}

ネイティブコードが含まれているパッケージはlinkを貼る必要があります。
例えば、 react-native-vector-icons が該当します。

$ react-native link

自動生成なので見る必要ないけど一応サンプルは下記で見れます。

mock作成する

下記のようなmockを作成します。

mock_1.png

データ構造

todoの構造を下記とします。

src/components/index.js
const todoList = [
  { id: 1, message: 'todo!', isComplete: false, },
  { id: 2, message: 'complete todo!', isComplete: true, },
  { id: 3, message: 'complete todo!', isComplete: true, },
];

React Native Elementsのパッケージを使ってみる

react-native-elementsCheckBox Componentを使って実装します。参考

配列をループさせてDOMを生成する場合は key を入れる必要があります。

title はチェックボックスに表示する文字列が入ります。
checked はチェックをつけるかどうかを判断します。
on~ はイベント処理を定義します。

src/components/index.js
const TodoCheckBox = (row) => (
  <CheckBox
    key={row.id}
    title={row.message}
    onPress={() => console.log('todoトグル')}
    onLongPress={() => {
      Alert.alert(
        '確認',
        `[${row.message}]を削除しますか?`,
        [
          { text: '削除しない', style: 'cancel' },
          { text: '削除', onPress: () => console.log('todo削除') },
        ],
      );
    }}
    checked={row.isComplete}
  />
);

Mock全体

View はwebで言う div 的なものだと思ってください。
View ではスクロールできないので ScrollView も使用します。

cssで画面のレイアウト設定を行います。参考
flex は超絶難しいので、詳しく知りたい人は調べてください。

react-native-elementsFormInput を利用しています。詳しく知りたい人は参照

src/components/index.js
export default class Todo extends Component {
  render() {
    const TodoCheckBox = (row) => (
      <CheckBox
        key={row.id}
        title={row.message}
        onPress={() => console.log('todoトグル')}
        onLongPress={() => {
          Alert.alert(
            '確認',
            `[${row.message}]を削除しますか?`,
            [
              { text: '削除しない', style: 'cancel' },
              { text: '削除', onPress: () => console.log('todo削除') },
            ],
          );
        }}
        checked={row.isComplete}
      />
    );

    return (
      <View style={{ flex: 1 }}>
        <View style={{ marginTop: 20, flexDirection: 'row'}}>
          <View style={{ flex: 5 }}>
            <FormInput
              autoCapitalize="none"
            />
          </View>
          <View style={{ flex: 1 }}>
            <Button
              onPress={() => console.log('add')}
              title="ADD"
            />
          </View>
        </View>
        <ScrollView style={{ flex: 1 }}>
          {todoList.map(TodoCheckBox)}
        </ScrollView>
      </View>
    );
  }
}

下記を実行して見た目を確認してください。

$ react-native run-ios

サンプルは下記でみれます。

ストアー作成する

ストアーとはデータを保持するクラスです。
計算処理やデータ取得処理、イベント処理などを書いたりもします。

@observable list = [] を利用して画面描画した際、変数変更時に自動的に画面に反映されます。

こちらを参照

src/stores/todo.js
import { observable } from 'mobx';

export default class TodoStore {
  @observable list = [
    { id: 1, message: 'todo!', isComplete: false, },
    { id: 2, message: 'complete todo!', isComplete: true, },
    { id: 3, message: 'complete todo!', isComplete: true, },
  ];
}

サンプルは下記です。

Provider でComponentを囲むと中でストアーを使うことができます。
詳細はこちら

src/main.js
import React from 'react';
+import { Provider } from 'mobx-react/native';
import Todo from './components';
+import TodoStore from './stores/todo';
+
+const stores = {
+  todo: new TodoStore(),
+};

export default () => (
-  <Todo />
+  <Provider {...stores}>
+    <Todo />
+  </Provider>
);

Componentに先程作った todo storeを渡していきます。

サンプルは下記です。

まず、モック用のデータ消去します。

src/components/index.js
- const todoList = [
-   { id: 1, message: 'todo!', isComplete: false, },
-   { id: 2, message: 'complete todo!', isComplete: true, },
-   { id: 3, message: 'complete todo!', isComplete: true, },
- ];

ストアーを使うためには, injectobserver を使用します。
おまじないとして覚えてください。
こちらを参照

src/components/index.js
+ import { inject, observer } from 'mobx-react/native';

+ @inject('todo')
+ @observer
export default class Todo extends Component {
  render() {
+   const { todo } = this.props;
    return (
         <ScrollView style={{ flex: 1 }}>
-          {todoList.map(TodoCheckBox)}
+          {todo.list.map(TodoCheckBox)}
         </ScrollView>

下記を実行して見た目が変わらないことを確認してください。

$ react-native run-ios

storeに入れているmessageの値を変えてreloadし直して画面に表示されるtodoが変わることを確認してください。

サンプルは下記です。

アクションを作成する

テキストフォーム内の値を保持するアクションを作成します。

MobXの @action を使うことで実装できます。

src/stores/todo.js
-  import { observable } from 'mobx';
+  import { observable, action } from 'mobx';

+  @observable inputText = '';
+
+  @action onChangeText(message = '') {
+    this.inputText = message;
+  }

Componentに上記のアクションと保持した値を利用できるできるようにします。

src/components/index.js
+            <FormInput
+              autoCapitalize="none"
+              value={todo.inputText}
+              onChangeText={(message) => todo.onChangeText(message)}
+            />

ここまでの変更でテキストフォームに値を入力できれば成功です。
ほんとにできているか気になる方は下記のようにコンソールデバッグメッセージを仕込んで呼び出されていることを確認してください。

src/stores/todo.js
  @action.bound onChangeText(message = '') {
+   console.log('message:', message);
    this.inputText = message;
  }

サンプルは下記です。

ストアをでテキストフォームの値を管理することができるようになったので管理している値でtodoを作成できるようにします。
ホントはサーバーサイド登録するべき、サンプルだから許される(魔法の言葉)

src/stores/todo.js
+  @action add() {
+    // 空文字の場合は登録しない
+    if (this.inputText === '') return;
+
+    this.list.push({
+      id: Math.random().toString(36).slice(-8),    // 雑にランダム文字列を生成
+      message: this.inputText,
+      isComplete: false,
+    });
+    this.inputText = '';
+  }

Componentに上記のアクションを利用できるできるようにします。

src/components/index.js
          <View style={{ flex: 5 }}>
            <FormInput
              autoCapitalize="none"
              value={todo.inputText}
              onChangeText={todo.onChangeText}
+             onSubmitEditing={() => todo.add()}
            />
          </View>
          <View style={{ flex: 1 }}>
            <Button
-             onPress={() => console.log('add')}
+             onPress={() => todo.add()}
              title="ADD"
            />
          </View>

ここまでの変更でテキストフォームにメッセージを入れてボタン押したタイミングでtodoが作成されれば成功です。
必要の亡くなったダミーデータを削除しましょう。

src/stores/todo.js
-  // TODO: とりあえずデータを入れておく
-  @observable list = [
-    { id: 1, message: 'todo!', isComplete: false, },
-    { id: 2, message: 'complete todo!', isComplete: true, },
-    { id: 3, message: 'complete todo!', isComplete: true, },
-  ];
+  @observable list = [];

サンプルは下記です。

次にチェックボックスのトグルを実装します。リスト内部のオブジェクトのisCompleteを反転させることで実現できます。(もっと良い実装があったら教えてください)

src/stores/todo.js
+  // 指定したidのコンプリートフラグを反転する
+  @action toggleComplete(id) {
+    this.list = this.list.map(o => o.id === id ? { ...o, isComplete: !o.isComplete } : o);
+  }

Componentに上記のアクションを利用できるできるようにします。

src/components/index.js
-        onPress={() => console.log('todoトグル')}
+        onPress={() => todo.toggleComplete(row.id)}

サンプルは下記です。

最後にtodoを削除できるようにします。 Array.filter をつかって一致する id を消します。

src/stores/todo.js
+  @action del(id) {
+    this.list = this.list.filter(o => o.id !== id);
+  }

Componentに上記のアクションを利用できるできるようにします。

src/components/index.js
-              { text: '削除', onPress: () => console.log('todo削除') },
+              { text: '削除', onPress: () => todo.del(row.id) },

サンプルは下記です。

すべての機能が動くことを確認しましょう。

$ react-native run-ios

発展(時間が余った人用)

現状のアプリは閉じたらtodoが全部吹き飛びます。これを AsyncStorage 等に入れられるようにしてみましょう。生のAPIを利用するのは苦痛なのでいい感じにラップされている react-native-store を利用して見るといいかもしれません。

後ろが真っ白で見づらいので、backgroundColorを設定してみましょう。

todoが下に入っていくのに違和感を感じる人は最新の登録が上に来るようにしてみましょう。

最後に

ハンズオンはこれで終わりです。
Nativeの話よりReactとMobXの話が中心になってしまった感じはしますが、ネイティブが関係しないコードであればほぼweb開発の知識で開発を始めることができると感じたと思います。
まだメジャーバージョンが出ておらず辛いところもあります。そこは頑張って行きましょう!

43
41
0

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
43
41

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?