{React Native} 単語帳アプリ GoogleAPI + unstated + AsyncStorage
React Nativeに手を出してから既に2か月ほど。とりあえずいろんな機能のチュートリアルを試してみたけれど、ようやくそれらを参考に単語帳アプリを自分で作ってみた。
ざっくりこんな感じ。
まだまだコードの書き方が拙いし(しかも参考にした数々チュートリアルの影響が色濃く残っているので一貫してない)、機能やUIにも欠陥が無数にあるし(ところどころアプリ感は出せても、ださい、、、)本来とても表に出せたものじゃない。けど、自分で取り組んでいてRNの日本語の参考例はもっと出回ってほしいと感じたし、あわよくば熟練者の方からアドバイスをいただけたらな、といった理由で公開することに。
本当はチュートリアル形式で紹介したかったけど、途中経過を各段階で載せてくのはさすがにしんどかった(チュートリアル記事の偉大さにしみじみ)ので、完成コードと要点紹介という形式で。
主に練習したかった機能としては
また、UIでは主に下記を活用。
- react-navigation
- react-native-action-sheet
- react-native-swipe-list-view
- expo-linear-gradient
ソースコードはこちら。
https://github.com/tkd708/language-app
さて、それでは個別にどんな感じで実装したか紹介!
まず翻訳機能を使うのにGoogleのAPI Keyが必要になります。取得方法は下記参照。https://cloud.google.com/translate/docs/quickstart-client-libraries
で、そのAPIが公開されちゃうと困るのでPublicにならないようにする処理はこちら。
(今回は実装し損ねたので、const API_KEY = を書き換えてやってくださいな)
https://dev.to/robertchen234/how-to-use-google-translate-api-27l9
さて、翻訳部分のfunctionはこんな感じで書きました。
(components/AddScreen.js)
API Keyに加えて、source=の先に翻訳元言語、target=の先に翻訳先言語を設定してq=の先に翻訳元のテキストを入れる感じです。
これらはUIのPickerとかTextInputで入ってくる状態としました(このコンポーネント内のみで完結)。
ちなみに、format=textを足しとかないと特殊記号('とか各種アクセント記号とか、フランス語だと頻出)が翻訳先で出てきたときに文字コードで返ってきちゃうので注意。
API使うのに定番のaxios、下記がシンプルで分かりやすかった。
Google Translate実装の部分で参考にしたのはこちら。
http://blog.zenof.ai/create-a-language-translation-mobile-app-using-react-native-and-google-apis/
Node.jsでの実行例なんかも参考にしてました。アウトプットの構造を勘違いしててsetStateでうまく保存されないみたいな凡ミスもしてました...
https://www.atmarkit.co.jp/ait/articles/1705/16/news019.html
https://www.nodejsera.com/how-to-use-google-translator-with-nodejs.html
- Unstatedによる状態管理
Google Translate APIを用いて単語を翻訳してタグもつけて(components/AddScreen.js)、検索した単語を一覧表示する(components/ListScreen.js)のに、これらのコンポーネント間で単語の情報をやり取りするのに状態管理をする必要が出てきます(よね?)。
そこで状態管理といえば王道はRedux。。。なのは間違いないと思われますが、下記の記事にある通りこの程度の試作アプリにはコード量も増えてファイル構造も複雑化してかえって苦労するので、より手軽なunstatedを採用。実際、Reduxより遥かに手軽だと感じました。
次の状態管理はReduxをやめてunstatedにする理由 - Qiita
1. Storeの役割も兼ねるContainerを用意する(importしたContainerをextends)
(containers/WordContainer.js)
Component間をまたいで参照するようなFunctionたちはこのContainerの中に格納しました。
2. 各ComponentからContainerをSubscribeする。
(下記の例ではAddScreenをWordContainerにSubscribe) (components/AddScreen.js)
3. 全体を<Provider>でWrapする。
(App.js)
こんなもんです。Containerに格納された状態や関数にアクセスするには、
this.props.AAA.state.BBB (AAAはSubscribe時に指定した、BBBは状態名)
this.props.AAA.CCC (AAAはSubscribe時に指定した定数、CCCは関数名)
といった書き方になる(はず、、、?上記記事ではrender内では始めにconst AAA = this.propsと
して、AAA.state.BBBやAAA.CCCでアクセスしている)
(components/AddScreen.js & ListScreen.js)
それなりに大規模アプリになって、コンポーネントも多数、開発人数も多数、となってこない限りはunstatedでいいんじゃないかなぁ、と思った次第。
- Asyncstorageでのローカルストレージを用いた単語帳とタグ機能
こちらの機能の実装はほとんどが下記のTodoアプリのチュートリアルから。
https://pusher.com/tutorials/build-to-do-app-react-native-expo
変更点のほとんどがJS関連のものでした。改めてRN書くにもJSが基本なんだなと実感。。。
(containers/WordContaienr.js)
AsyncStorageだとJSON形式のみなので、逐一Stringify(上記内ではsaveItems)とParse(上記内ではloadingItems)しなくちゃいけないからあまりお勧めされないらしい。次はSQLiteとか使いたいところ、、、
https://qiita.com/kaba/items/569aafd80889bb5d9328
このJSON縛りのせいだからか(?)、onDoneAddItemのところで、ID(uuidから生成)をkeyとし、翻訳前後の単語やタグなどの情報をvalueとする、入れ子のobject構造をprevStateに書き足していくという形式をとっている。そして更新されたStateをsaveItemに送ってAsyncStorageに書き込む。
これらはTodoアプリ記事に倣っていて、変更したのはallItemsだけでなくtaggedItemsのObjectを増やした点。加えて、saveItemsとloadingItemsの中にあるmergedTags(全単語から付加されたTagをすべて一つの配列にPushしたもの)、filteredTags(filterで重複をはじいたもの)、そして最後にallTags(filteredTagsをsetState)することによってタグ一覧を生成してる。onTagPressはタグを選んだときのFunctionで、一致するタグを持つ単語を配列(ここではwordsWithTag)に入れるという作業をforで回して全探索して、その配列を別の状態で保存する(ここではtaggedItems)というやりかた。
これらのFunctionのs下の方にDeleteItem, CompleteItem, IncompleteItemと続きますが参考にしたTodoアプリ記事と丸被りなのでここでは省略。keyにしたIDを用いて削除や完了の操作をしている。
機能面で主要な点はこんなところです。引き続いてUIに活用したLibraryの紹介!
- react-navigation
これは、このアプリ全体、そして自分のReactNative学習全般を通してとても参考にしている下記のシリーズから。SplashScreen(Splashって本来はアプリ起動前に現れる画面のこと?)とか、ほぼそのままです。非常に丁寧な解説、感謝しております。
https://note.mu/cube0529/n/n4a130029dfe1
また、下記のシリーズも分かりやすく、また今回は採用しなかったけどReduxの実装例もついているのでおすすめです。
React-Navigatorを利用してみる(基礎編) - Qiita
- react-native-swipe-list-view
単語帳の表示の仕方として、翻訳前の単語を一覧にして、確認したい単語の訳をパッと引き出せる形式がよい、と思っていたとろで巡り合ったLibrary。Swipeして削除とかのボタンを表示させるアレ。
下記の開発元の具体例が充実してるのでそのまま。今回はその中のStandAloneRowを採用。CSS(styles)での調整がメインかな。
https://github.com/jemise111/react-native-swipe-list-view
- react-native-action-sheet
タグ一覧を表示するのに、当初modalを使っていたけどこちらに変更。そのままnpmに記載の例から。
https://www.npmjs.com/package/@expo/react-native-action-sheet
Library内のshowActionSheetWithOptionsというメソッドに、options(画面に表示される文字列での選択肢)と、そのindexそれぞれに対する操作(buttonIndex)を指定する。今回はoptionsにタグ一覧に加えて、Show all wordsという全単語表示の選択肢を用意。それによってIndexが一つずれるのでonTagPressに送るタグのindexはbuttonIndex - 1。
(components/ListScreen.js)
また、アプリの上位層で<ActionSheetProvider>によるWrapが必要。
(App.js)
...後から知ったのが、類似のreact-native-actionsheetというlibraryもあるようで、しかもこっちの方がgithubのスターも多いようで、、、
https://github.com/beefe/react-native-actionsheet
- expo-linear-gradient
背景色をグラデーションにするというもの、複数色指定可能。これも先述のTodoアプリの記事を参考にしつつ、またアプリ全体にこの背景を適用するには上層でWrapすればよいとの記載をみつけたので今回はその形で実装。
さてさて、長くなってしまったけれどこんな感じでした!
改めてJSとCSSが基礎であることを痛感するとともに、何よりただチュートリアルをなぞるだけじゃなくそれらを組み合わせて自作する(多くは転用ですが)ことは格段に工夫を要するし一段階上の練習になるということを実感。
なるべく自作しながら必要だったり興味のある機能を調べながら実装していく、というのが現段階では大切なのだろうと思います。
最後に、参考にさせていただいた数々の貴重な記事とその作成者への感謝と、この記事とコードも稚拙ながら誰かの一助となるよう願いを込めて。