Vue.js + TypeScript覚え書き

# 概要

Vue.js + TypeScriptに関する覚書。インストールオプション等は後で変えるかもしれません。

# バージョン

* Node.js : 12.18.1
* TypeScript(tsc) : 3.9.3
* vue : 2.6.11
* vue-router : 3.2.0
* VueCLI(@vue/cli) : 4.4.6
* axios : 0.18.0
* element-ui : 2.4.5

# プロジェクト作成と起動

``` shell
$ vue create training
================================================
Vue CLI v4.4.6

### TypeScriptを使うためManuallyを選ぶ
? Please pick a preset: (Use arrow keys)
  default (babel, eslint)
? Manually select features

### 「TypeScript」「Router」を選択
? Check the features needed for your project:
 ◯ Babel
 ? TypeScript
 ◯ Progressive Web App (PWA) Support
 ? Router
 ◯ Vuex
 ◯ CSS Pre-processors
 ◯ Linter / Formatter
 ◯ Unit Testing
 ◯ E2E Testing

### Noを選択(class-styleではなくVue.Extendを使った記法にする)
### https://github.com/vuejs/rfcs/pull/17#issuecomment-494242121
? Use class-style component syntax? (Y/n) n

### Noを選択(自動検出されたポリフィルにBabelとTypeScriptを使用しないため)
### ポリフィル : 古いブラウザーで、最近機能を使えるようにするためのコード
### https://developer.mozilla.org/ja/docs/Glossary/Polyfill
### https://www.sejuku.net/blog/100946
? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? n

### Yesを選択(Historyモードを使う。Hashモードは若干速くなるがURLに「#」が入る)
### https://qiita.com/kozzzz/items/af9ad63fa70d4724cc2a
? Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n) Y

### 「In package.json」を選択(パッケージを全てpackage.jsonで管理するため)
? Where do you prefer placing config for Babel, ESLint, etc.?
  In dedicated config files
? In package.json

### Noを選択(毎回プロジェクトを作成する毎に設定を選ぶため)
? Save this as a preset for future projects? (y/N) n

### 起動
$ cd training
$ npm run serve
================================================
```

# 初期のディレクトリ構造

``` shell
training
|-- README.md
|-- node_modules
|-- package-lock.json
|-- package.json
|-- public
|   |-- favicon.ico   # ファビコン
|   `-- index.html    # index.htmlのテンプレート
|-- src
|   |-- App.vue
|   |-- assets        # webpackに処理されるアセットを格納するディレクトリ
|   |   `-- logo.png
|   |-- components
|   |   `-- HelloWorld.vue
|   |-- main.ts
|   |-- router
|   |   `-- index.ts
|   |-- shims-tsx.d.ts
|   |-- shims-vue.d.ts
|   `-- views
|       |-- About.vue
|       `-- Home.vue
`-- tsconfig.json
```

* /public
    * [webpackにより管理されないアセットを入れるディレクトリ](https://dev83.com/vue-constr/#public)
    * ビルド時にdistディレクトリに配置されたりする
* /src
    * App.vue
        * アプリケーション全体で使うナビゲーションをここで設定
        * viewsディレクトリに格納したページデータはApp.vueを通して表示する
    * /components
        * 単一ファイルコンポーネント(.vueなど)を格納
        * アプリケーション内で何度も使うようなパーツを入れる
    * main.ts
        * Vueの基本設定ファイル。App.vueをhtmlに紐付ける。
    * /router/index.ts
        * ルーティングを設定
        * VueリソースのURLやURLに紐づくコンポーネント等を設定
    * shims-tsx.d.ts
        * IDEでjsx syntaxsupportを有効にして、JSXスタイルのTypeScriptコードを記述できる
        * 実は末尾が`d.ts`で終わるファイルであればファイル名は何でも良い
    * shims-vue.d.ts
        * VueファイルをTypeScriptで利用可能とするファイル
        * webpackを使わない場合 `import Foo from "./Foo.vue";` はTypeScriptはモジュールであることを理解できない
        * 実は末尾が`d.ts`で終わるファイルであればファイル名は何でも良い
    * /views
        * ページデータの(.vueなど)を格納する

# ファイルの内容

## /src/App.vue

``` shell
<template>
  <div id="app">
    <div id="nav">
      <router-link to="/">Home</router-link> |
      <router-link to="/about">About</router-link>
    </div>
    <router-view/>
  </div>
</template>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}

#nav {
  padding: 30px;
}

#nav a {
  font-weight: bold;
  color: #2c3e50;
}

#nav a.router-link-exact-active {
  color: #42b983;
}
</style>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20474/e0c60c42-42bd-29f6-5ee0-b7d3b56c4f12.png)

## /src/view/About.vue

``` shell
<template>
  <div class="about">
    <h1>This is an about page</h1>
  </div>
</template>
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20474/798abd84-73bc-d1b9-c78d-af11056e1b7a.png)

* 一番上に「App.vue」のtemplateが出力されている
* その下に「About.vue」のtemplateが出力されている


## /src/view/Home.vue

``` shell
<template>
  <div class="home">
    <img alt="Vue logo" src="../assets/logo.png">  ロゴを表示
    <HelloWorld msg="Welcome to Your Vue.js App"/> ※1
  </div>
</template>

<script>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'

export default {
  name: 'Home',    // nameは自身のファイル名から.vueを除いたものをつける？
  components: {
    HelloWorld
  }
}
</script>
```

* ※1
    * HelloWorldコンポーネントを出力
    * msgには「Welcome to Your Vue.js App」の文字列が入る

## /src/components/HelloWorld.vue

``` shell
<template>
  <div class="hello">
    <h1>{{ msg }}</h1>      // 「Home.vue」でmsgに代入した値が入る
    <p>
      For a guide and recipes on how to configure / customize this project,<br>
      check out the
      <a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
    </p>
    <h3>Installed CLI Plugins</h3>
    <ul>
      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-typescript" target="_blank" rel="noopener">typescript</a></li>
      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-router" target="_blank" rel="noopener">router</a></li>
    </ul>
    <h3>Essential Links</h3>
    <ul>
      <li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
      <li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
      <li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
      <li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
      <li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
    </ul>
    <h3>Ecosystem</h3>
    <ul>
      <li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
      <li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
      <li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
      <li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
      <li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
    </ul>
  </div>
</template>

<script lang="ts">
import Vue from 'vue';

export default Vue.extend({
  name: 'HelloWorld',    // nameは自身のファイル名から.vueを除いたものをつける？
  props: {               // 親コンポーネントから子コンポーネントにデータを受け渡す
    msg: String,
  },
});
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
  margin: 40px 0 0;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
</style>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20474/67bdacca-cf1f-e2cf-2b12-300fe821cc37.png)


* [props(プロパティ)](https://www.e-loop.jp/knowledges/3/)
    * 親コンポーネント(App.vue)から子コンポーネント(HelloWorld.vue)にデータを受け渡すときに使う
    * 流れ
        * 子コンポーネント(HelloWorld.vue)のscriptタグ内でプロパティ名を列挙
            * ここでは `msg: String` のみ
        * 子コンポーネント(HelloWorld.vue)のtemplateタグ内でプロパティを記載
            * ここでは `<h1>{{ msg }}</h1> `
        * 親コンポーネント(Home.vue)のtemplateタグ内で子の呼び出しとデータの受け渡し
            * ここでは `<HelloWorld msg="Welcome to Your Vue.js App"/>`

## /src/router/index.ts

``` shell
import Vue from 'vue'
import VueRouter, { RouteConfig } from 'vue-router'
import Home from '../views/Home.vue'

Vue.use(VueRouter)

  const routes: Array<RouteConfig> = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  }
]

const router = new VueRouter({
  mode: 'history',             // historyモードで利用
  base: process.env.BASE_URL,
  routes
})

export default router
```

## /src/main.ts

``` shell
import Vue from 'vue'
import App from './App.vue'
import router from './router'

Vue.config.productionTip = false

new Vue({
  router,
  render: h => h(App)
}).$mount('#app')
```

# 単語帳

* [Babel](https://qiita.com/koedamon/items/3e64612d22f3473f36a4)
    * JavaScriptのコードを新しい書き方から古い書き方へと変換するツール
* webpack
    * Node.jsでサーバーサイドで動かすモジュールバンドラーツール。Node.jsのライブラリ
    * [JS、CSS、画像ファイルを一つのJSファイルにまとめてくれる](https://qiita.com/kamykn/items/45fb4690ace32216ca25#webpack-%E3%81%AF-jscss%E3%82%92%E4%B8%80%E3%81%A4%E3%81%AE%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AB%E3%81%BE%E3%81%A8%E3%82%81%E4%B8%8A%E3%81%92%E3%81%BE%E3%81%99)
* npx
    * [npm 5.2.0からの新機能](https://qiita.com/tonkotsuboy_com/items/8227f5993769c3df533d#:~:text=npm%E3%81%A8%E3%81%AFNode.js,%E3%81%A7%E3%81%8D%E3%82%8B%E3%82%88%E3%81%86%E3%81%AB%E3%81%AA%E3%82%8A%E3%81%BE%E3%81%99%E3%80%82)
    * ローカルにインストールしたnpmパッケージを、npxコマンドだけで実行できる
        * それまではPATHを調べて実行したりとちょっと面倒だった
        * 例 : `npx webpack -v`