東京都 新型コロナウイルス感染症対策サイトを読む その2
これの続きをやっていく
知ってることでもちゃんと改めて自分の言葉で書く
ポート3000はRailsで使ってしまうので、ポート5000に変更してやっている
nuxt.config.ts
server: {
port: 5000
}
トップページ
Nuxt.jsは pages
ディレクトリ内のVueファイルの木構造に沿って、自動でルーティングを作成してくれる
ということでトップページは pages/index.vue
が該当する
レイアウトは layouts/default.vue
単なるVue.jsのファイルなので、以下の3つのセクションに分かれている
<template>〜〜〜</template>
<script lang="ts">〜〜〜</script>
<style lang="scss" scoped>〜〜〜</style>
- JSはTypescriptで書かれている
- CSSはscssで書かれていて、Vue.jsのscoped CSSで書かれている
- scoped CSSは意外とバッティングするとか何とか話があるが、CSS Modulesとどっちがいいんだろうね?
- レイアウトはscopedになってなかった
ということがわかった
ローディング画面
アプリを起動して最初に目に付くのがロード画面なので、それがどうなっているのか確認する
通常ロード画面はレイアウトの最上段に配置されるのが普通だと思うので、 layouts/default.vue
を見るといかにもそれっぽいものがある
<v-overlay v-if="true" color="#F8F9FA" opacity="1" z-index="9999">
<div class="loader">
<img src="/logo.svg" alt="東京都" />
<scale-loader color="#00A040" />
</div>
</v-overlay>
v-overlay
は属性ではないのでディレクティブではなく、Vuetify
が提供するコンポーネントVuetify
はパッケージを入れて、nuxt.config.ts
のbuildModules
に突っ込めば即使えるようになるっぽいVue.use(Vuetify)
的なことはいらないんだー。へー。
- 当たり前だけど、
v-overlay
だけ置いても何も表示されないので、loader
クラスのdivタグが本体 scale-loader
はvue-spinner
により提供されており、'vue-spinner/src/ScaleLoader.vue'
からimportしているのがわかる- サンプルかわいい
default.vueでよくわからないところ
せっかくなので、このままデフォルトレイアウトファイルを眺めていく
そして現時点ではっきりと説明できないところをチェックしていく
LocalData
のところ
この部分↓
type LocalData = {
hasNavigation: boolean
isOpenNavigation: boolean
loading: boolean
}
data(): LocalData {
let hasNavigation = true
let loading = true
if (this.$route.query.embed === 'true') {
hasNavigation = false
loading = false
} else if (this.$route.query.ogp === 'true') {
hasNavigation = false
loading = false
}
return {
hasNavigation,
loading,
isOpenNavigation: false
}
},
まず、 type LocalData = 〜
の部分だけど、これはTypescriptの機能を使っているだけ(参考)
なぜ interface
ではないのかはわからない(勉強しておきます)
では data(): LocalData {〜}
はというと、これは型を宣言的に書く手法のようだ(Vue.jsでの参考)
すなわちこいつもTypescriptの記法
通常 data
は以下のように定義するが
data() {
return {
count: 'a'
}
},
これを↓のように記述することができる
data
は関数なので、その戻り値の型を定義しているということになる
で、上の例では { count: number }
の部分が LocalData
に置き換わっているということ
data(): {
count: number
} {
return {
count: 0
}
},
Typescriptだけで書くとこれと同じような感じ
class Hoge {
test(): { count: number } {
return { count: 123 }
}
}
const a = new Hoge
console.log(a.test()) // => { count: 123 }
methods
内や head
でも同じ記述が使われているが、これと同じ意味
なお、ここで strictもしくはnoImplicitThisをtrueにする必要あり
と書かれているが、このアプリでも tsconfig.json
でtrueが設定されている
- headメソッド
いつのまにか新しいメソッドでも生えたのか!?とか思ったけどそんなわけなかった
こいつはNuxt.jsのAPIで、HTMLのheadタグを設定することができる便利なやつらしい
- getMatchMediaメソッド
まず window.matchMedia('(min-width: 601px)')
の部分で「画面の横幅が601px以上であるかどうか」を判定することができる
window.matchMedia('(min-width: 601px)').matches
でbooleanが返るので、よくif文とかの条件に指定されている
ここでは window.matchMedia('(min-width: 601px)').addListener(this.hideNavigation)
のようにチェーンされているが
こうすることで「指定した画面幅になったら指定された関数を実行する」ことができるようになる
すなわち「画面の横幅が601px以上になったら、hideNavigation
関数を実行する」ということ
スマホみたいに狭い画面からPC表示に切り替わったときとかに発動する
そして matchMedia
の戻り値は MediaQueryList
なので、 getMatchMedia
関数にはその型が指定されている
コロナ対策サイトは601pxを境にレイアウトが切り替わるようになっている模様
- MetaInfo
vue-meta
によって提供されている
このへんに合致するオブジェクトが返る必要がある
- locale(i18n)周り
nuxt-i18n
を使って多言語対応を行なっている
設定ファイルはルートにある nuxt-i18n.config.ts
strategy: 'prefix_except_default'
が指定されているので、日本語の場合はURLに/jaなどのプレフィックスをつけなくてもOK- その他の言語の場合はURL末尾に
/en
などのプレフィックスを付与する必要がある
- その他の言語の場合はURL末尾に
detectBrowserLanguage
によってnuxt-i18n
が勝手にブラウザの言語を検出して、いい感じにリダイレクトしてくれる(すごい)useCookie
をtrueにすることで、検出・設定した言語をCookieに保存してくれるcookieKey
はCookieのキー名
- localeファイルは
assets/locales
に存在している(キーが日本語なの斬新だった) $t
は単なる変換で、$tc
は複数形に対応することができる、$te
は定義のある・なしでboolが返る- 基本的に
$t
を使えばよさげ - 結構
$tc
使ってるところあるけど、複数形とか関係なさそうで謎(ただの使い方間違い?)
- 基本的に
default.vueで読み込まれているコンポーネントを眺める
- DevelopmentModeMark
process.env
の値を見て開発画面かどうかを表示しているコンポーネント- コンポーネントオプションとして
name
とprops
とcomputed
がある(computed
は特に書くことないのでスルー)name
- コンソールから
console.log
とかを使って、Vueコンポーネントを調べた際、VueComponent
というデフォルトの名前で表示されるが、name
を指定することで、指定した名前でわかりやすく表示してくれるオプション
- コンソールから
props
- 親から子へデータを渡す時に定義するオプション
- DevelopmentModeMarkコンポーネントは
value
という名前で値(文字列)を受け取ることができる(必須ではない) - しかし、受け取った値を使っている箇所がないのでなんの為に存在するのか謎(誰か理由を教えてくれ)
- どういうわけか手元の環境ではこのコンポーネントの表示がされない
- と思ったらissueが立ってた => https://github.com/tokyo-metropolitan-gov/covid19/issues/3068
- ScaleLoader
- ↑でも書いたけどローディング画面に出てるアニメーション
- SideNavigation
- サイドメニューのコンポーネント
- 画面幅が601px未満だと(
SideNavigation-Body
の部分が)非表示になる $emit
で親(default.vue
)のメソッドを呼んでいる<v-icon class="SideNavigation-OpenIcon" :aria-label="$t('サイドメニュー項目を開く')" @click="$emit('openNavi', $event)" >
- この部分がクリックされると、
$emit('openNavi', $event)
の部分が発火し、親であるdefault.vue
が子コンポーネント(SideNavigation
)を描画している<side-navigation :is-navi-open="isOpenNavigation" :class="{ open: isOpenNavigation }" @openNavi="openNavigation" @closeNavi="hideNavigation" />
- の部分の
@openNavi="openNavigation"
が呼ばれる openNavigation
はdefault.vue
のメソッドなので、その処理が行われる- なお、
@click="$emit('openNavi', $event)"
の第二引数である$event
は、親のopenNavigation
の引数として利用可能(ここでは使ってないけど) - 参考 - やわらかVue.js
- 参考 - 親と子の引数を渡すこともできる
- NoScript
- JSがオフになっている人向けのコンポーネント
その他細かいこと
import XXX from '@/hoge/fuga.vue'
の@
はアプリルートを示している:属性="〜〜〜"
はVueの省略記法で、v-bind:属性="〜〜〜"
と等価@イベント="〜〜〜"
も省略記法で、v-on:イベント="〜〜〜"
と等価- 以下の部分を例にすると
<side-navigation :is-navi-open="isOpenNavigation" :class="{ open: isOpenNavigation }" @openNavi="openNavigation" @closeNavi="hideNavigation" />
:is-navi-open
は子コンポーネント(SideNavigation
)に値を渡している(props):class
は条件によってopen
クラスを付与したり、外したりしている(ただの属性)- 単にクラスを付与するだけなら
class="hoge"
とbindする必要はない
- 単にクラスを付与するだけなら
@openNavi
は子コンポーネントによって呼び出される($emit("openNavi")
)イベント
長くなってきたのでとりあえず今回はこんなところで