英語学習用タイピングWebアプリをReactで作った話
今回は前々から作っていたタイピングWeb アプリ、 Study Typer について話していきたいと思います。
React, Redux, Python, Flask, Redis などを使っています。
アプリの概要や特徴を説明をしたあとに技術的な点についてお話します。
Study Typer って?
ずばり、英語学習者のためのWebタイピングゲームです。以下のリンクで遊べます。
http://study-typer.macinjoke.com/
様子です〜 pic.twitter.com/enc9ganrJF
— まっきー@バイト探し中 (@macinjoke) 2018年9月16日
Study Typer は1ページのみのWebアプリです。
左側に英単語が書かれてあり、右側にその単語の日本語の意味が書かれています。
ユーザは意味を確認しながらタイピングをすることで英語の勉強になります。打つのは英語の方なので、英語タイピングの練習にもなります。
(英単語のタイピングの早さというのはその単語を何回打ってきたかで決まると思います。例えば foreign という単語打ち慣れていないとめちゃくちゃタイポしません?)
下の方にあるRankパラメータは1~30 まであり、英単語の難易度を変更できます。1は超簡単で7くらいで結構難しいです。(現時点では10以上になると単語数少なくてあまり表示されません)
1画面で10個の単語を打てるようになっていて、10個目が終わったら1個目に戻ります。
次の単語に進むと英単語の音声が流れます。発音の確認もできて便利ですね。
ゲームとして微妙じゃね?
はい、そのとおりです。ただいまこのアプリは 製作途中 なので微妙です。今後以下のような機能、改善を考えています。
- そもそものUI改善(一気に英単語10個表示している意味なくね?)
- 良い日本語意味データにする( 良さげな英語データが欲しい(情報求む) · Issue #1 · macinjoke/study-typer-frontend · GitHub )
- ログイン機能
- タイムアタック、スコア
- 各単語の打ち始めから打ち終わりまでの時間のみ計測することで日本語意味をしっかり読みながら競える(学習用のタイピングゲームなので)
なので時間のあるときにゆっくりと作っていきたいと思っています。
ご意見や、機能提案、バグ報告、プルリクなどWelcome ですのでよろしくお願いします!
技術的なお話 〜どうやって作ったか〜
コードはGithub に上がっています。
構成
Study Typer はReact を使ったSPAです。APIサーバにはPythonのFlask を利用しています。英単語データを保存するDBにはRedis を使いました。
システム構成図をお見せします。
たいしたことないアプリなのに構成はしっかりしました。
Dokku
Amazon のEC2 のUbuntu上にDokkuというHeroku ライクなPaaS を簡単に立ち上げるツールを入れています。Dokku上のメインアプリとして、Python, Flask を使ったAPIサーバを立ち上げています。Dokku のRedis Plugin を使ってRedis を立ち上げ、Flaskアプリと繋げています。(繋げると、アプリ側の REDIS_URL という環境変数に値が入るのでそれを使って接続できます)
もちろんGit Push で楽チンデプロイできます。
Dokku を使った構成はコマンド何回か叩けばポンとすぐできるので楽でした。
なぜHeroku でやらなかったか
Heroku 使えばいいじゃんってなるんですけど、Heroku だとまあまあお金を払わないとRedis のデータを永続化することができません。今回Redis のデータは永続化したかったので他の方法を探しました
Redis
Redis にはランクをつけられた英単語とそれに対応する日本語意味のデータが格納されています。ブラウザからのリクエストによってFlaskサーバがRedisからデータを取得し、レスポンスを返します。データの更新は管理者(僕) しかしません。
なぜ数あるDBの中でRedis を選んだかという話ですが、それは単にRedisを使ってみたかったからです。
というのは半分冗談で半分本当なのですが、後々Redisの利点をちゃんと考えてみました。
まず、この程度の規模のアプリでRDBを使うには重すぎるので無しです。
そしてMongoDB のようなドキュメント型のデータベースと比べるとRedis の方がレスポンスが早いです。 Redis はインメモリのデータベースで、永続化機能を弱める代わりに速度を上げています。この永続化の欠点ですが、今回のアプリでは問題になりません。ちなみにRedis の永続化にはRDB方式とAOF方式という2つの方法があります。
( 永続化について詳しくはこちら Redis の永続化 — Redis Documentation (Japanese Translation) )
今回、RDB方式を使っていますが、AOF方式を使っていようがどちらにしてもデータの更新を行うのは管理者(僕) だけなので、頻繁に起きません。保存(永続化)をするタイミングは僕がデータの更新をするときだけで良いので永続化機能によって速度が落ちるような心配はなくなります。
また、Redis のデータモデルは Key / Value なので、複雑なデータ構造を作るのは大変という欠点がありますが、適切なデータタイプを使えば頑張れば作れます。
Redis でもRDBの代替になれるほど複雑なデータを扱えるという例として以下の公式のチュートリアルがわかりやすかったです。
例えば Study Typer では 英単語の集合を words:[rank] というキーでSorted Sets 型のデータで定義しています。(例: words:1, words:2, words:30)
これで 任意のランクの英単語のリストを取ってくることができるようになります。以下のような形になっています。
words:1 = [ attack, cook, ask, come , ... , ]
そして word:[word] というキーで Hash 型のデータを定義しています。 (例: word:attack, word:cook, word:boondoggle )
これで任意の英単語の情報が取れます。以下のような形になっています。
word:attack = { rank: 1, ja: 攻撃する }
Redis はデータの検索はできないのでキーを指定して特定のデータを取ってくるという単純なことしかできません。よって必要なデータの形式であらかじめデータを持っておかねばならないということです。
色々語りすぎましたが、Redis 使ってみて、結果的に良かったんじゃないかと思ってます。実際に英単語取ってくるの早いし。(測ってないし他と比べてないから分からないけど)
S3 とCloudFront
静的コンテンツの配信はAmazon のS3 とCloudFront (CDNサービス) を使いました。最初はS3だけでとりあえず良いかなと思ってたのですが、S3だけで公開した結果、英単語の音声が鳴るのが遅い(音声の読み込みが遅い)という問題が起きたので、CloudFront も使うようにしました。その結果ちゃんと一瞬で英語音声が鳴るようになりました。CloudFrontの設定はそんなに難しくなく手軽でしたし、S3使うときはCloudFront パパっとやった方が良さそう。 こういう話もあるし → S3にCloudFrontを通すことで月20万ぐらい節約した話
React の実装
ここからが本番ですね。みんな大好きフロントエンドの実装のお話です。
ちょっと文字ばかりになったので癒やされる画像をはさみます。
Flow
今回は Flow を初めて使ってみました。最初は戸惑いましたが、ReduxのリポジトリにtodoアプリのFlow版サンプルがあったので、これを参考に型付けしていったら良い感じになりました。
redux/examples/todos-flow at master · reduxjs/redux · GitHub
漸進的型付けを初めてやりましたが、慣れるとかなり良さそうという印象でした。慣れるまではどう型を付けたら良いか分からなかったりしそうですが、やるうちにコツが掴めてくる気がします。コードが肥大化して自分が書いたコードを忘れるくらいになってから見ると改めて型の偉大さがわかるような気がしました。補完もバリバリ効くのがGoodです。
Redux
そしてご存知 Reduxを使っています。ユーザが打ち込んだ文字や英単語のデータを管理しています。正直この規模のアプリではReduxは不要な感じがしましたが、これからスケールアップする可能性と、Redux 使うほうがもはや慣れているし使っちゃえって感じでした。Reduxについては慣れてきたのでちゃんとした設計になっていると思いますね。非同期処理はthunk でやっています。
CSS
CSS系はCSS Modules も styled-components も使わず、雑にless, bootstrap を使ってます。あんまり時間かけたくなかったので(ゆーてCSSは慣れてないからいつも時間かかる)。次のプロダクトは CSS in JS に挑戦したいですね。
Prettier
Lint 系は Prettier を使いつつ airbnbのコンフィグをextends してちょっとだけ修正してます。まあ .eslintrc 見るのが早いですね。Prettier 最高ですねこれ。1行に入る文字数を考えて整形 してくれるというのが良い。これでコードフォーマットについて考えたり議論する時間は減りそうです。
迷える子羊たちよ、Prettier に従え。
Jest
テストはJest を使ってReducer 周りだけテストしてみました。初めてJest 使いましたが、カバレッジとる機能とかデフォルトでついてて使いやすいなと思いました。UIのテスト(Enzyme)はまだUIの変更がこれからバリバリあるかもしれないのでだるくてやってません。
こんな感じに最近のちゃんとしたフロントエンド開発というのを意識して構成的にはナウいものをキャッチアップしていったつもりです(CSS周り以外)。
まとめ
1年半前はフロントエンドのフの字も知らない(jQuery 2週間触った程度) でしたが、一応ここまでできるようになりました。基本的に独学ですが、研究室の先輩からの教えや、バイトでの経験が役に立ちましたね。 ここ数ヶ月はWebフロントエンド本当に楽しくなってきて重点的に勉強しています。キャリア的にもフロントエンドエンジニア目指したいなと思うようになりました。(少し前は自分のスキル的にフロントエンドはオマケ程度に考えてました)
これからは次のWebアプリのアイディアもあるので次のアプリ作るか、Study Typer をさらにブラッシュアップするか、どっちかをしていく予定です。 自分の性格的に、動けば良いというよりもコードがどうなっているのか、設計がしっかりしているかという点が気になり、そこを突き詰めるのに時間を使いすぎる癖があります。良いか悪いか何とも言えないですが、そのせいでインプットばかりしている気がするので、もっとアウトプットの速度を早めていきたいなと思いますね。(Webフロントエンド、インプット面白くないですか?)
そしてただいま無職の大学院生1年生ですので、Webフロントできるアルバイト探しています。
え?研究はしなくても良いのかって?研究も最近頑張ってますよォォォ!ウォォォ!
何か感想とか意見がありましたら @macinojke や Github のIssue にドシドシお願いします。 こういう構成の方が良いよーとかのツッコミ欲しいです。 あとはこれですね... 良さげな英語データが欲しい(情報求む) · Issue #1 · macinjoke/study-typer-frontend · GitHub
ではまた。