はじめに
インターンの事前課題で React Native を使ってネイティブアプリを作る機会に恵まれました。うろ覚えではこの事前課題を突破すると面接に呼ばれて、その面接を突破するとインターンでコードレビューとかしてくれる感じだったと思います(わかる人にはどの企業かわかるかもね)。今はまだ事前課題提出した段階です。
企業側としては React Native 経験がない学生を想定してくれているらしく、参考になるサイトを教えてくれたり、LINEでガンガン質問に答えたりしてくれます。JavaScriptそこそこ使ってるつもりだったけど何もわかってないんだなあってよくわかったし、デバッグのときの考え方、注目する部分などなど勉強になることがとても多かったです。
インターン課題自体のソースコードは公開できないと思いますが、ここで得た学び・ハマった点をできるだけ抽象化して記録しておこうと思います。(もし成果物を公開していいようだったら追記します!)
React Native とは
簡単に言うと、「JavaScriptだけでネイティブアプリを作れるフレームワーク」ってことになるんでしょうか。本来ネイティブアプリを作る際には、iOSアプリだとSwiftやObjective-C、AndroidだとJavaやKotlinという感じでOSによって使う言語が違うわけですが、React Native のすごいところは「同じソースコードで両方のOSに対応できる」点だと思います。処理はJavaScript、配置はXMLのように書けるので割と直感的ですし、App.jsを編集してシミュレーターを更新するだけで更新内容が確認できます。console.logをChromeのコンソールに表示させることもできますし、かなり作りやすい印象を受けました。
学び / ハマった点
チュートリアルは偉大
今更かって言われると思いますが、ステップアップしながら教えてくれるチュートリアルって本当にありがたいですね。React Nativeのチュートリアルは英語ですが、単語調べながらゆっくり読んでいけば十分理解できると思います(僕ができたので)。React Native のチュートリアルはこちら: https://facebook.github.io/react-native/docs/getting-started
トップレベルの要素はひとつ
https://utano.jp/entry/2016/07/react-adjacent-jsx-elements-must-be-wrapped-in-an-enclosing-tag/ に詳しい説明がありますが、Reactの仕様上このようなコードを書くと怒られます。
import React, { Component } from 'react';
import { View, Text } from 'react-native';
export default class App extends Component {
render() {
return (
<View>
<Text>1つめのView</Text>
</View>
<View>
<Text>2つめのView</Text>
</View>
);
}
}
エラーは Adjacent JSX elements must be wrapped in an enclosing tag
です。この場合、トップレベルにViewコンポーネントが2つ並んでいるので、この2つをまとめるコンポーネントが必要になります。
import React, { Component } from 'react';
import { View, Text } from 'react-native';
export default class App extends Component {
render() {
return (
<View>
<View>
<Text>1つめのView</Text>
</View>
<View>
<Text>2つめのView</Text>
</View>
</View>
);
}
}
コンポーネントのpropsに指定するのは、関数の「定義」
Buttonコンポーネントがあるとします。
export default class App extends Component {
render() {
return (
<View>
<Button
title = "ボタン"
onPress = /*ボタンが押されたときに実行される関数*/
/>
</View>
);
}
}
ここでonPress
の部分に書くのは、
onPress = { Alert.alert("hoge") }
ではなく
onPress = { () => {Alert.alert("hoge")} }
です。関数形式にしないと、この場合ことあるごとにアラートがでて大変なことになります。
AsyncStorageからとってきたものは文字列
AsyncStorageというのは、端末内のストレージです。アプリの内容を保存しておくことで、アプリがリロードされたときも前の内容を引き続き表示することができます。
AsyncStorageから情報を取ってきていじることを考えます。
AsyncStorage.getItem('user1', (err, result) => {
value = result;
})
console.log(value) // {"name":"taro","age":25}
console.log(value.name) // undefined
2つのログを比較するとなんで???って思うんですが、これ要はvalue
はただの文字列なんですよね。2行目で
value = JSON.parse(result);
としてやることで解決します。AsyncStorageからとってきたものは真っ先にJSON.parse()
してやるのがよさそう。
逆に、AsyncStorageに保存するときにはJSON.stringify()
をかけてやる必要がありますし、AsyncStorageには文字列を保存できるということは、こういうことができます(邪道かな?)
// 誕生日が来たらユーザー情報の年齢が加算されるようにしたい
// {ユーザーID: {name: 名前, age: 以前+1}}
newUserDataString = '{"' + users[userId].id + '":{"name":' + (users[userId].name) + ',"age":' + (users[userId].age + 1) + '}}';
// {"taro":{"name":"taro","age":26}}
AsyncStorage.setItem(users, usersInfo, (err, result) => {
AsyncStorage.mergeItem(users, newUserDataString, (err, result) => {
AsyncStorage.getItem(users, (err, result) => {
usersObj = JSON.parse(result);
console.log(usersObj);
});
});
});
なお、オブジェクトの要素にアクセスする際のブラケット記法を使うことで、要素名がころころ変わる場合でも対応できます。
render() は props / state が変更されるたびに実行される
ので、非同期処理を行っていたりして実行順が変わってしまった場合に、setState()
で指定した変数が返ってくる前にsetState()
が実行されるとエラーが起きたりします。
async/awaitなどについては完全に理解できているわけではないので、今後の課題とさせてください、、要は実行順がバグの温床になるよって話です。
子コンポーネントで親コンポーネントのstateを変更する
propsを使って関数を受け渡してやることで実現できます(ちょっとトリッキーじゃないですか?)。
class child extends Component {
render() {
return (
<View>
<Button
title = "change state"
onPress = this.props.changeState
/>
</View>
);
}
}
export default class parent extends Component {
// constructorは省略
changeStateFunc() {
this.setState(/*hoge*/);
}
render() {
return (
<View>
<child
changeState = { () => { this.changeStateFunc(); } }
/>
</View>
);
}
}
future work
JavaScriptについてちゃんと学ぼうと思いました。具体的にはthis
やasync/await
など。
あとはSwiftも触ってみて、React Native との比較ができたらいいなと思います。