「引っ越しするけど会社から歩いて10分圏内ってどこまで?」を調べるサービスをつくりました
こうです。
新しい住居を探そうとしたとき、「会社から歩いてちょうど 30 分のところに住めたら QOL 高くない?」と思いました。(運動できる、公共交通機関と無縁、買い物もできるetc...)
でも ある一点を中心とした移動可能エリアを知りたいとき、円でざっくりと表示をする以外のアプローチがほぼない ことに気がつきました。海外サイトも含めかなり調べてみましたが、類似のサービスは見当たりません。
という経緯でつくったのが How far can I go? というサイト。
ぽちぽちやっていただければわかりますが、結果の精度はかなりのものと思われます。特に道の有無や川沿いなどを検証してもらえるとその効果がすぐにわかるかと。
転職先が決まっているなら、そこにピンを差して交通手段と所要時間を設定してください。HOME'S などの住宅検索サービスには条件絞り込みをしたあとにそれらを地図上にまとめてマッピングできる機能があるので、それらと組み合わせると色々捗るというわけでございます。
ぜひブックマークしていただいて、新たな生活を検討する おとも にどうぞ…!
以下技術要素および所感などをまとめています。
Vue 3 の新機能をフル活用した
Vue 3 の目覚ましい新機能の数々をほとんど使いました。具体的には Vue 3 公式ガイドにある項目です:
新機能全体についてわかりやすく読みやすい記事をお探しの場合、つい少し前に投稿された ゆきさんの記事 が素敵です。
Composition API
もうみなさんもこの字面に飽き飽きしているころかもしれませんが、使ってみたらめちゃ強力でした。本当にすごい改善だと思います。
僕らが享受できるメリットを超簡潔にまとめるなら どんなにコードを書いても迷子にならない というところでしょう。
これまでの Vue では、Vue インスタンスと呼ばれるものの中で目的ごとにグルーピングされた記述が求められていました。こんな具合です:
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: {
...,
}
},
data () {
return {
repositories: [],
filters: { ... },
searchQuery: ''
}
},
computed: {
filteredRepositories () { ... },
repositoriesMatchingSearchQuery () { ... },
},
watch: {
user: 'getUserRepositories'
},
methods: {
getUserRepositories () { ... },
updateFilters () { ... },
},
mounted () {
this.getUserRepositories()
}
}
これはつまり「コンピュータの都合にあわせてプログラムの挙動ごとに分類している」状況であり、僕らが本当に望んでいるはずの「アプリケーションのビジネスロジックごとの分類」ではありませんでした。
Composition API はこの課題を解決するために考えられました。
setup()
という関数にこれまでの記述をすべて内包させることで、コンポーネント内でのグルーピングをすべて開発者に委ねます。関数を値として利用できる JavaScript の特性を活かしたよいインターフェースなんですよね。
export default {
setup() {
const isLoggedIn = ref(false)
const onClick = (() => {
...
})
const styleBind = computed(() => {
...
})
return {
isLoggedIn,
onClick,
styleBind,
}
}
}
新しくコンポーネントを作るたびに各プロパティセットを記述したりなど苦労していた部分がほとんど省かれ、純粋な JavaScript にかなり近づきました。縦に伸びていくコンポーネントを見ると記述の追加が心理的に嫌になっていくあの現象もほぼなくなったと言えます。
でもこれだとまだ弱い。
<script setup>
記法
この記法は先ほどの setup()
関数の糖衣構文なのですが、使わない理由が見当たらないほど最高 なのでぜひみなさん使ってください。公式でも利用が推奨されています。
純粋な setup()
関数がいまだ持っていたと思われる問題は以下です:
- なぜか宣言した各種値を return で返さないといけない(明示のメリットの小ささに対してという意)
- 変数を返すだけの return が一番下にあることにより、せっかく関心の分離をしたのに毎回上下に移動しないといけない状況が改善されていない
- この return は縦に伸びていくだけで開発者は中身を意識することがなくなっていき、無駄を感じ始める
-
components()
の定義って本当に二重に必要? -
props()
の中の型定義はもうちょっと TypeScript ネイティブにしてもらえない?
これらを解決するのが <script setup>
記法なのです。
…と、ちょっと長くなりかけているのであとは こちらの記事 に全面的に助けていただくことにしましょう!
あと地味に嬉しい点として、<script setup>
内の階層が常にトップレベルになる というものがあります。
最も多くコードを書くこの領域がエディタのインデントとして一番左にあることの嬉しさはわざわざ僕が説明するまでもないでしょう。
Suspense
コンポーネント自体が非同期であった場合、コンポーネントレベルで処理分岐ができるようになるものです。たぶん下記を見れば一発でわかります:
<template>
<suspense>
<template #default>
<!-- Your main component! -->
</template>
<template #fallback>
<!-- Component for loading -->
<template>
</suspense>
</template>
これはつまり 子コンポーネント側でトップレベル await を使える ということを意味しており、前述の setup 記法と組み合わせると本当に便利になります。
公式からは「まだ仕様が安定していないので本番使用は控えてください」とアナウンスされていますが、耐えきれず使ってしまいました。今後の動向を気にしておきます。
Teleport
モーダルなど明らかにコンポーネントの場所に依存しないけど各コンポーネント内から制御したい…というものがたしかに存在しました。
これをラクに解決してくれるのが Teleport です。
<teleport to="body">
<ModalBack v-if="isOpenModal" />
</teleport>
「こういう単目的のための機能追加ってどうなの?」という向きもあると思うのですが、フロントエンド開発に確実に存在するニーズだと思いますし、それをうまく Vue の仕組みに乗せて解決するこのアイデアは純粋に好きになれました。ストアの使用量も減るし積極的に使っています。
CSS 変数
「Vue はせっかく同じファイル内に CSS も書いているんだから CSS の記述内部でも JS みたいな世界観が引き継ぎればいいのにな」とずっと思っていたのですが、その第一歩のような機能でした。
setup()
から任意の値を返して、
<script setup lang="ts">
const color = ref("#ff0000")
</script>
それを <style>
タグ内でバインドします:
.blocks {
width: 300px;
height: 200px;
background-color: v-bind(color);
...
}
あまり多用すると混乱を招きますし、使わざるを得ない状況になっているならおそらくもっとよい設計がある兆候でもあると思うのですが、ピンポイントで使っていきたい素敵な機能です。
状態管理
純粋な Vue 3 内の機能ではないんですが、使わなかった目玉機能の1つに状態管理ライブラリ Pinia があります。
これまで使われていた Vuex では型サポートを加えるのに一手間面倒があり、なおかつストアをモジュール分割しようとするとこの手間が倍増しました。実際僕も面倒ですべて index.ts につくってしまったアプリもあります。
今回はストアの規模も極小だったので見送ったのですが、ほぼ Composition API の感覚で書けるという噂なので次は絶対に使おうと思っています。楽しみ。
--
後日追記:別のプロジェクトで Pinia をひと通り利用することができたので使い方をまとめた記事を書きました!
使用した API リソースなど
アプリケーションの主軸となる API についてです。
TravelTime
「あのエリア表示どうやってんの?」とずーっと気になっていたと思います。
これ。
Google Maps API を使いあるポイントから一定距離離れた地点とのルート検索をし、所要時間が入力された時刻にヒットするまでそれを繰り返す→それを 360° ぐるっと回るまでひたすら API を叩き続けてポイントを集め続ける ……わけではなく、 このニーズのために開発されたであろうズバリな海外サービスを見つけたのでそれを使いました。
ただ1つ決定的な問題があって、死ぬほど高いんですよね。
なんと 43万円払いましたww
下に小さく「年払いしかないよ!」と邪悪なことが書いてある。
まあこうなってしまったのは完全に僕がおバカなだけで、ほぼ完成してから「あれ、これフリープランじゃ無理じゃね?🤔」となっただけです(10 req/min がキツい)。作るのは2週間もかかっていないくらいなのですが、せっかく作ったしもう払っちゃおう!くらいのノリでした。
実際のところ本当に便利で実用的なものを作れたと思っていますし、類似サービスも(少なくとも一般の人は)見つけられないと考えると価値は十分にあるのかなーと。
払ったからには宣伝も積極的にしていきたいです。僕を哀れに思ってくださる方はぜひご友人などにもシェアしてください…。笑
使ってくれる人が増えたら PWA もやってみるとか、あとは Cordova でビルドしてみるのも面白そうかなーなど思ってます。
Geocoding
ジオコーディング(住所や地名などから座標に変換する作業のこと)には Google Maps API を使いました。AWS にも類似のサービスがあるのですが、データの有用性においてさすがに Google 側の圧勝と言わざるを得なかったです。
住所表記というのは凄まじい表記ゆれ幅を考慮しなければならず、なおかつ言語が違うとその特性もまるで変わります。Amazon Location Service でそれをチューニングするのは至難の業と思えたのでだいぶ助かりました。
今回は取得結果の表示にも一工夫入れてみました。ユーザビリティはどうだったでしょうか…!
その他もろもろ
そのほか小さいことです。
ダークモード
ダークモードを実装しました。
Google Maps API で地図を読み込むとき、スタイルセットを設定できるんですよね。
new google.maps.Map(document.getElementById("map") as HTMLElement, {
center: center,
zoom: 15,
styles: shouldDarkMode() ? darkStyle : null,
})
しめしめと思って「dark style」的な感じで漁ってみたらあっさり見つかったので対応させました。あとは自分で置いた各パーツ類も一緒に変更させるだけです。
シークレット管理
「アプリケーション全体のリポジトリを公開しているのに API キーとかどうやって管理しているの?」という質問を以前同僚にされたことがあるので書きます。
やることは
- gpg を使う
- 公開鍵暗号方式ではなく対称鍵暗号方式のみを使う(要はパスワードでの開け閉め)
- 忘れてもいい最強のパスフレーズを GitHub の Repository secrets に登録しておく
- GitHub Actions でそれを参照して復号化させる
です。
「パスフレーズだけでセキュリティ担保できるの?」とか「その運用って継続性あるの?」とかはここでは割愛させていただきます。詳しくは別に記事を書いてあるのでご興味ある方はぜひどうぞ。
おわりに
まあとにかく Vue 3(と setup 記法)がよすぎる ということに尽きます!!
機能追加や修正が楽しくてどんどん書けちゃうし、いくら書いてもメンテナンス性が全く落ちないことに心底感動します。1回 Vue から離れた過去がある人にはなおさら触ってほしいです。
型についても同じです。「『TypeScriptと相性が悪い』というのは過去のものになった」とみなさんも色んなところで見かけるようになったと思いますが本当にそうですし、「コードを書く」という作業自体の体験もぐんと向上しています(エディタのサポートなど)。
自分の仕事は AWS 系なので会社でフロントエンドを書いている人などまわりには一人もおらず、共感してもらえる相手がいなくていつも寂しい思いをしています…。この記事を読んでくれた方が少しでも Vue に興味を持ってくれたら嬉しいです!
お読みくださりありがとうございました。
お引っ越しの際はぜひご利用ください~
Discussion
すごい!!