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のインストールに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.jsとindex.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
デコレーター使いたい、スプレッド演算子使いたい、その他諸々の理由で下記に変更します。
{
- "presets": ["react-native"]
+ "presets": [
+ "react-native",
+ "react-native-stage-0"
+ ],
+ "plugins": [
+ "transform-decorators-legacy"
+ ]
}
ネイティブコードが含まれているパッケージはlinkを貼る必要があります。
例えば、 react-native-vector-icons
が該当します。
$ react-native link
自動生成なので見る必要ないけど一応サンプルは下記で見れます。
mock作成する
下記のようなmockを作成します。
データ構造
todoの構造を下記とします。
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-elements
の CheckBox
Componentを使って実装します。参考
配列をループさせてDOMを生成する場合は key
を入れる必要があります。
title
はチェックボックスに表示する文字列が入ります。
checked
はチェックをつけるかどうかを判断します。
on~
はイベント処理を定義します。
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-elements
の FormInput
を利用しています。詳しく知りたい人は参照
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 = []
を利用して画面描画した際、変数変更時に自動的に画面に反映されます。
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を囲むと中でストアーを使うことができます。
詳細はこちら
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を渡していきます。
サンプルは下記です。
まず、モック用のデータ消去します。
- const todoList = [
- { id: 1, message: 'todo!', isComplete: false, },
- { id: 2, message: 'complete todo!', isComplete: true, },
- { id: 3, message: 'complete todo!', isComplete: true, },
- ];
ストアーを使うためには, inject
と observer
を使用します。
おまじないとして覚えてください。
こちらを参照
+ 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
を使うことで実装できます。
- import { observable } from 'mobx';
+ import { observable, action } from 'mobx';
+ @observable inputText = '';
+
+ @action onChangeText(message = '') {
+ this.inputText = message;
+ }
Componentに上記のアクションと保持した値を利用できるできるようにします。
+ <FormInput
+ autoCapitalize="none"
+ value={todo.inputText}
+ onChangeText={(message) => todo.onChangeText(message)}
+ />
ここまでの変更でテキストフォームに値を入力できれば成功です。
ほんとにできているか気になる方は下記のようにコンソールデバッグメッセージを仕込んで呼び出されていることを確認してください。
@action.bound onChangeText(message = '') {
+ console.log('message:', message);
this.inputText = message;
}
サンプルは下記です。
ストアをでテキストフォームの値を管理することができるようになったので管理している値でtodoを作成できるようにします。
ホントはサーバーサイド登録するべき、サンプルだから許される(魔法の言葉)
+ @action add() {
+ // 空文字の場合は登録しない
+ if (this.inputText === '') return;
+
+ this.list.push({
+ id: Math.random().toString(36).slice(-8), // 雑にランダム文字列を生成
+ message: this.inputText,
+ isComplete: false,
+ });
+ this.inputText = '';
+ }
Componentに上記のアクションを利用できるできるようにします。
<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が作成されれば成功です。
必要の亡くなったダミーデータを削除しましょう。
- // 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を反転させることで実現できます。(もっと良い実装があったら教えてください)
+ // 指定したidのコンプリートフラグを反転する
+ @action toggleComplete(id) {
+ this.list = this.list.map(o => o.id === id ? { ...o, isComplete: !o.isComplete } : o);
+ }
Componentに上記のアクションを利用できるできるようにします。
- onPress={() => console.log('todoトグル')}
+ onPress={() => todo.toggleComplete(row.id)}
サンプルは下記です。
最後にtodoを削除できるようにします。 Array.filter
をつかって一致する id
を消します。
+ @action del(id) {
+ this.list = this.list.filter(o => o.id !== id);
+ }
Componentに上記のアクションを利用できるできるようにします。
- { 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開発の知識で開発を始めることができると感じたと思います。
まだメジャーバージョンが出ておらず辛いところもあります。そこは頑張って行きましょう!