# Vue.js チュートリアル <style> summary{ color: gray; } </style> ### 参考リンク集 ドキュメントがあちこちにあって分かりにくいのでまとめました 必要になったら見てください - [Vue.js 公式ドキュメント](https://jp.vuejs.org/v2/guide/): Vueの文法全般 - 例などは全てjavascriptで書かれているので、TypeScriptの場合は違う書き方をすることが多いです ([後述](#TypeScriptの使用)) - [スタイルガイド](https://jp.vuejs.org/v2/style-guide/index.html): 変数名、コンポーネント名などの慣習的な部分について - [Vue Loader 公式ドキュメント](https://vue-loader-v14.vuejs.org/ja/): 単一コンポーネントファイル (`*.vue`) について知りたい場合はこっち - [Vue CheatSheet](https://www.vuemastery.com/pdf/Vue-Essentials-Cheat-Sheet.pdf): 基本文法を2ページにまとめた公式のチートシート とても便利 - [Vue Mastery](https://www.vuemastery.com/courses/intro-to-vue-js/vue-instance): 公式の解説動画 - 英語だけど難しい単語は出てこないし図とかがとてもわかりやすい、1回が5分程度なのでさくさく進める Intro to Vue.js を最後までやればある程度のものは作れるようになる - めちゃくちゃ暇だったらやってみてください - その他 (非公式) - [Vue.jsでビューの変更がされないときに疑うこと+主な解決策方法 - Qiita](https://qiita.com/bobu_web/items/ec5a98d03758d12ad721) ## 環境構築 ### 前提 node.js と git がインストールされている必要があります。 `npm --version`, `git --version` でそれぞれバージョン情報が表示されればok 入っていない場合、node.js のインストールは [ダウンロード | Node.js](https://nodejs.org/ja/download/)、gitのインストールは [Git - Gitのインストール](https://git-scm.com/book/ja/v2/%E4%BD%BF%E3%81%84%E5%A7%8B%E3%82%81%E3%82%8B-Git%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB) からできます ### プロジェクトのセットアップ 既存プロジェクトをクローンしてくる場合 (今回など) は必要ないですが一応書いておきます 読まなくて大丈夫です <details> <summary> クリックして開く </summary> <div> 今回作ったプロジェクトは `vue-example` という名前にしました #### vue-cli のインストール 新規プロジェクトのセットアップをしてくれるツールです ``` $ npm install -g @vue/cli ``` #### プロジェクトの作成 Vueがサポートしている機能のうち、プロジェクトに必要なもののみが含まれたテンプレートを作成します ``` $ vue create vue-example Vue CLI v4.1.1 ? Please pick a preset: default (babel, eslint) ❯ Manually select features ``` defaultは本当に最低限の機能のみがついたものです 今回は他にも使いたい機能があるので、下の `Manually select features` を選択します。 (上下キーで動かしてEnterで選択) ``` ? 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 ``` Babel, Linter/Formatter はデフォルトでonになっています 他に選択したものは下の通りです - TypeScript: スクリプト部分はJavaScriptの代わりにTypeScriptを使って記述する - Router: ルーティングのためにVue-Routerライブラリを使う - Vuex: 状態管理のためにVuexライブラリを使う (今回の開発で使うかどうかまだ決めかねているけれど、後から追加するのは面倒なので一応入れておく) - CSS Pre-processors: スタイル部分はCSSの代わりにSCSSまたはSassを使って記述する ``` ? Use class-style component syntax? (Y/n) Y ``` TypeScriptでVueを記述する際の文法を選ぶ 今回はクラススタイルを使うことにします ``` ? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, trans piling JSX)? (Y/n) Y  ``` これはあまり必要なさそうですが念のため入れておきます ``` ? Use history mode for router? (Requires proper server setup for index fallback in product ion) (y/N) n ``` Vue Router にはHashモードとHistoryモードの2種類があって、Hashモードのほうが実装が楽なので今回はこっちにします ``` ? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default ): Sass/SCSS (with dart-sass) ❯ Sass/SCSS (with node-sass) Less Stylus ? Pick a linter / formatter config: ESLint with error prevention only ESLint + Airbnb config ESLint + Standard config ❯ ESLint + Prettier TSLint (deprecated) ``` このあたりは好みなので普段自分が使っているものを選びました ``` ? Pick additional lint features: ❯◉ Lint on save ◯ Lint and fix on commit ``` 毎回のセーブ時にLint (フォーマッター) が走るようにします ``` ? Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys) ❯ In dedicated config files In package.json ``` 追加した各種機能の設定をどこに記述するか これも好みですが別々のファイルにあったほうがわかりやすいので上を選びます Babelの設定は babel.config.js, ESLintの設定は .eslintrc.js に保存されます ``` ? Save this as a preset for future projects? (y/N) n ``` プリセットとして保存すると、同じような新規プロジェクトを次に作成する時にワンタップでできるようになります 今回は必要ないので保存しません #### 作成したプロジェクトをGitHubに上げる 作成されたディレクトリには既にgitが入っているので、リモートリポジトリを登録してpush ```shell $ git remote add origin git@github.com:yto60/vue-example.git $ git push origin -u master ``` URL: https://github.com/yto60/vue-example.git ### eslint等の設定 .eslintrc.js を修正して、以下の設定を加えます このあたりは好みなので必須ではないです - ダブルクォート ( `"` ) ではなくシングルクォート ( `'` ) を使用 - 文末セミコロンなし - インデントはスペース2つ分 修正内容: [edit eslintrc · yto60/vue-example@d69a5dc](https://github.com/yto60/vue-example/commit/d69a5dc1406d52a1a671749faf777a85e50bf2a5) ### eslint --fix の実行 上で修正したeslintの設定に従ったフォーマットになるようにlintを走らせます ```shell $ eslint --ext 'ts,vue' --fix src ``` ### axiosのインストール APIリクエストを投げるために使うHTTPクライアントライブラリとして、axiosをインストールする ``` $ npm install --save axios ``` 詳しくは[後述](#axios1) </div> </details> ### Hello, World テンプレートのリポジトリ (https://github.com/yto60/vue-example.git) をクローン ```shell $ git clone https://github.com/yto60/vue-example.git ``` クローンしたリポジトリに移動 → 必要なパッケージのインストール → 開発用サーバーを起動 ```shell $ cd vue-example $ npm install $ npm run serve (略) No type errors found Version: typescript 3.5.3 Time: 4709ms App running at: - Local: http://localhost:8080/ - Network: http://172.29.55.175:8080/ ``` 指定されたURL (デフォルトは `localhost:8080` ) をブラウザで開いて、下のような画面が表示されればok ![](https://i.imgur.com/J2VF53a.png) <br> ### Vue-Devtoolsを開いてみる Vue-Devtools という拡張機能がChromeとFirefoxにあるので、入れることをお勧めします それぞれの変数にどの値が入っているかなどを見ることができます とりあえずは飛ばしても大丈夫です <details> <summary>クリックで開く</summary> - Chrome: [Vue.js devtools - Chrome ウェブストア](https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd?hl=ja) - Firefox: [Vue.js devtools – 🦊 Firefox (ja) 向け拡張機能を入手](https://addons.mozilla.org/ja/firefox/addon/vue-js-devtools/) インストール後、さっき開いたlocalhostのページをブラウザで開いて、右クリック → 「検証」 → 「Vue」タブを選択 ![](https://i.imgur.com/96flz20.png) <br> こんな感じの画面が出てきたらokです :tada: ![](https://i.imgur.com/Wn1Reby.png) 参考: [Vue-Devtools — Vue.js](https://jp.vuejs.org/v2/cookbook/debugging-in-vscode.html#Vue-Devtools) </details> <br> ### ESLintの設定 複数人で開発する場合、コードをきれいに保つためにlintを使います とりあえず飛ばしても大丈夫です <details> <summary>クリックで開く</summary> 以下はVSCodeの場合です 1. クローンしてきたリポジトリをVSCodeで開く 1. 以下の2つの拡張機能をインストールしてウィンドウをリロード (Command + R) (既にインストール済みの場合は飛ばす) - [Vetur - Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=octref.vetur) - [ESLint - Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) 1. Command + P でコマンドパレットを起動 1. `> Open Workspace Settings` でワークスペース設定を開く 1. 右上にあるこのアイコン (![](https://i.imgur.com/VBU9hs8.png)) をクリックして設定 (JSON) を開く 1. 下をコピペ ``` { "editor.formatOnSave": false, "editor.codeActionsOnSave": { "source.fixAll": true }, } ``` これでlintが動くようになるはずです #### 確認 クローンしてきたリポジトリの `src/views/Home.vue` の13行目の `'` を `"` に変えてみると、 ![](https://i.imgur.com/oAzin7j.png) こんな感じで怒られます この状態でセーブ (Command + S) します `"` が `'` に戻り、赤い波線が消えればokです 同様に、 - 文末に `;` がある - インデント幅が2になっていない 場合はlintから怒られるはずです </details> </div> ## サンプルコード https://github.com/yto60/vue-example - `src/components/MyHelloWorld.vue` を作りました - `master` ブランチ: `MyHelloWorld.vue` の中身は真っ白です テスト用に使ってください - `src/views/Home.vue` の `HelloWorld` を全て `MyHelloWorld` に書き換えると、トップページに MyHelloWorld コンポーネントが表示されるようになります - [`sample` ブランチ](https://github.com/yto60/vue-example/tree/sample): 下の実装例を置いています - `.vue` ファイルを新規作成する際は `src/components/Template.vue` をコピペして使えます ## Vue.js 概観 ### ざっくりとした仕組み `/src` 以下のファイルにコードを書く → これらをビルド (HTML, js, cssに変換) するとブラウザで表示できるようになる #### ビルド用コマンド - `npm run serve` : 開発用 - ファイルのビルドと、開発用サーバーの起動を行うコマンド - ファイルを監視して、変更があるたびにビルドを行う (ファイルを編集するとブラウザの表示も変わる) - `npm run build` : 本番用 - ビルドのみを行う - ビルドされたファイルは `/dist` 以下に保存される (`/dist` ディレクトリは初回実行時に作成される) - 開発時はほとんど使わない ### ディレクトリ構造 ``` |- <プロジェクト名> |- src: ソースコードは基本的にこの中 |- view: 各ページ (URL) に対応するコンポーネント |- components: ページ内で使われるコンポーネント |- router: vue-router 関係 |- store: Vuex 関係 |- assets: 静的ファイル |- App.vue: 一番最初に呼び出されるコンポーネント |- main.js |- public: 静的ファイル (webpackを通らずにそのまま配置される) |- dist: ビルドしたファイルはここに配置される (gitでは管理しない) |- node_modules: 外部パッケージ (gitでは管理しない) |- package.json: パッケージの管理用 |- package-lock.json: パッケージの管理用 |- vue.config.js: vue-cli 関係の設定 ``` - コンポーネントの `import` 時など、`/src` 以下のファイルを使いたい時は、エイリアスとして `@` が使えます - 例: https://github.com/yto60/vue-example/blob/master/src/views/Home.vue#L10 ```vue import MyHelloWorld from '@/components/MyHelloWorld.vue' ``` ### 単一コンポーネントファイル (\*.vueファイル) 書き方 ```vuejs= <template> <!-- HTML --> </template> <script lang="ts"> // TypeScript </script> <style lang="scss"> // SCSS (またはCSS) </style> ``` ## 文法 ### データ (script) - コンポーネント内で宣言する変数 - `(変数名): (型) = (初期値)` - 型推論が効くので `: (型)` は省略可能 - 初期値なしで宣言だけしたい場合は、`undefined` を許容することを表すため `(変数に入れたい値の型) | undefined` とする - stringまたはundefined, の意味 (参考: [共用体型(Union Types)](http://js.studio-kingdom.com/typescript/handbook/advanced_types#union_types)) ```vue= <script lang="ts"> export default class MyHelloWorld extends Vue { val: string = 'foo' // val = 'foo' でも結果は同じ val2: string | undefined // 初期値を設定しない場合 } </script> ``` - スクリプト内での呼び出しは `this.(変数名)`, テンプレート内での呼び出しは `(変数名)` <br> ### テンプレート (template) [テンプレート構文 — Vue.js](https://jp.vuejs.org/v2/guide/syntax.html) - HTMLの中に変数などを埋め込みたい時に使う - 例: String型の変数 `var` を埋め込む ```vue= <template> ... <div>{{ var }}</div> <div>{{ var === 'foo' ? 'A' : 'B' }}</div> ... </template> ``` - `{{ }}` の中はTypeScriptとして解釈されるので、上のように三項演算子を使ったりもできる <br> > 以降、例の中の `<template>` `</template>`, `<script lang='ts'>` `</script>` は省略します <br> ### ディレクティブ (template) #### `v-if` `v-show` ==条件つきレンダリング== - [条件付きレンダリング — Vue.js](https://jp.vuejs.org/v2/guide/conditional.html) を読んでください - 基本は `v-show` を使うと思います - その要素がソースコードに存在するのも困る場合 (見せたくない情報が入っているなど) は `v-if` を使います #### `v-for` ==リストレンダリング== - [リストレンダリング — Vue.js](https://jp.vuejs.org/v2/guide/list.html) を読んでください - 特に注意 - [#オブジェクトの変更検出の注意](https://jp.vuejs.org/v2/guide/list.html#%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E5%A4%89%E6%9B%B4%E6%A4%9C%E5%87%BA%E3%81%AE%E6%B3%A8%E6%84%8F) - 「変数は変わっているのに表示が変わらない」みたいなことが起こった時はこれをやっていることがそこそこある - [#v-for と v-if](https://jp.vuejs.org/v2/guide/list.html#v-for-%E3%81%A8-v-if) - `v-for` と `v-if` を同じタグに適用させては**いけない** - ある配列の一部分のみを使って表示させたい、みたいな場合はv-ifではなくその配列をフィルターさせたものを使う - [フィルターされたリストの繰り返し処理 · yto60/vue-example@a4074aa](https://github.com/yto60/vue-example/commit/a4074aa5969e3b9b8fb9cd8b4126ee4743a79a7a) - 56行目の `get activeUsers()` は算出プロパティ ([後述](#computed-%E7%AE%97%E5%87%BA%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3-script)) です #### `v-bind` ==バインディング== - [テンプレート構文 — Vue.js #属性](https://jp.vuejs.org/v2/guide/syntax.html#%E5%B1%9E%E6%80%A7) - HTML**属性**の中に変数などを使いたい時に使う - 例: String型の変数 `dynamicId` をHTML属性のidとして使う ```vue= <div v-bind:id="dynamicId"></div> <div :id="dynamicId"></div> ``` - よく使われるため、糖衣構文として `v-bind:id` の代わりに `:id` が用意されている - 練習ではどちらでも良いですが、本番は `:"id"` のほうを使うようにしてほしいです (統一のため) - `:id` の他にも、 `:class`, `:href`, `:url` (画像のURL指定), などが使える - 例: string型の変数 `dynamicClass`, `dynamicColor` を使う ```vue= <div :class="dynamicClass"></div> <div :class="`has-color-${dynamicColor}`"></div> ``` - 動的に決められるclassやidに対してうまくCSSを設定すると、色などを動的に変えることができます - [バインディング · yto60/vue-example@a1de674](https://github.com/yto60/vue-example/commit/a1de674000a4ff282ccb2735b393812a60b64c38) - 参考: [CSSのセレクタチートシート | webliker](https://webliker.info/css-selector-cheat-sheet/) #### `v-on` ==イベント処理== - buttonやinputなどで発生したイベントに対して何かの処理をしたい時に使う - [イベントハンドリング — Vue.js](https://jp.vuejs.org/v2/guide/events.html) の「インラインメソッドハンドラ」までを読んでください - 実装例: [イベント処理 · yto60/vue-example@a8bdd29](https://github.com/yto60/vue-example/commit/a8bdd290d84aefc477db9714b0c6cd8617c1b836) #### `v-model` ==フォーム入力== - ユーザーの入力を受け取り、入力に応じてリアルタイムで変数を更新する、みたいなことができる - 今回のプロジェクトではかなり使いそう - [フォーム入力バインディング — Vue.js](https://jp.vuejs.org/v2/guide/forms.html) を読んでください - 実装例: [フォーム入力バインディング · yto60/vue-example@88aee2d](https://github.com/yto60/vue-example/commit/88aee2d64f92ef80504be619f7672d72fbb4bdb2) - 参考: [\<label\> - HTML: HyperText Markup Language | MDN](https://developer.mozilla.org/ja/docs/Web/HTML/Element/label) <br> ### 算出プロパティ (script) - [算出プロパティとウォッチャ — Vue.js](https://jp.vuejs.org/v2/guide/computed.html) を読んでください ```vue= get 関数名(){ 返したい変数の処理 } ``` 例: https://github.com/yto60/vue-example/blob/sample/src/components/MyHelloWorld.vue#L144-L147 <br> ### Props 親 → 子 へのデータの受け渡し (script) - 以下を読んでください - [コンポーネントの基本 — Vue.js #プロパティを使用した子コンポーネントへのデータの受け渡し](https://jp.vuejs.org/v2/guide/components.html#%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%97%E3%81%9F%E5%AD%90%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E3%81%B8%E3%81%AE%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AE%E5%8F%97%E3%81%91%E6%B8%A1%E3%81%97) - [プロパティ — Vue.js](https://jp.vuejs.org/v2/guide/components-props.html) の「単方向のデータフロー」まで - 実装例: [Propsによるデータの受け渡し · yto60/vue-example@42cd311](https://github.com/yto60/vue-example/commit/42cd3119a6fbeb3c61a2256144eaade2d3eea405) - MyMessage.vue の39行目で使っているのは [文字列リテラル型](http://js.studio-kingdom.com/typescript/handbook/advanced_types#string_literal_types) です #### 子コンポーネント ```typescript= import { Component, Prop, Vue } from 'vue-property-decorator' @Component() export default class (子コンポーネント名) extends Vue { @Prop({ type: (型のインターフェース) required: true // 必須で要求する場合 default: (デフォルトの値) // デフォルト値を設定する場合 }) readonly (変数名): (入れたい値の型) | undefined // 必須で要求する or デフォルト値が設定されている 場合はundefinedは不要 } ``` - 「単方向のデータフロー」に書いてある通り、子コンポーネントからPropの値を変更することはしてはいけないので、readonlyな変数として宣言しておく (上の11行目) #### 親コンポーネント - テンプレート ```vue= <(子コンポーネント名) (Prop名)="(渡したい値)" :(Prop名)="(渡したい値の入っている変数)" /> ``` - v-bindと組み合わせると、変数や算出プロパティなどを渡すこともできる - `(Prop名)="1"` だと渡されるmessageIdはstring型、`:(Prop名)="1"` だとnumber型になります - requiredなPropを渡していない場合はエラーになりますが、そうでないPropは何も渡さなくてもエラーは出ません - スクリプト ```typescript= import { Vue, Component } from 'vue-property-decorator' import MyMessage from '@/components/MyMessage.vue' @Component({ components: { (子コンポーネント1), (子コンポーネント2), ... } }) export default class (親コンポーネント) extends Vue { // ... } ``` <br> ### Emit 子 → 親 へのイベント発火 (script) - [コンポーネントの基本 — Vue.js #子コンポーネントのイベントを購読する](https://jp.vuejs.org/v2/guide/components.html?#%E5%AD%90%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E3%81%AE%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E3%82%92%E8%B3%BC%E8%AA%AD%E3%81%99%E3%82%8B) を読んでください - 実装例: [Emitによるイベント発火 · yto60/vue-example@322ece6](https://github.com/yto60/vue-example/commit/322ece67d32a624e1c7eac40d10db6f9b6569476) #### 子コンポーネント ```typescript= // methods @Emit((イベント名)) (関数名)() { return (イベントに渡す値 (無い場合は不要)) } ``` #### 親コンポーネント ```vue= <(子コンポーネント) @(イベント名)="(行う処理 or 実行するメソッド)" /> ``` - input等へのイベント処理と同じく、`@(イベント名)=""` の中にそのまま処理を書くこともイベント発火時に実行してほしいメソッドを指定することもできる - 引数をとるメソッドを実行したい場合は、子コンポーネントの (イベントに渡す値) のところに引数を入れる <br> ### スロット - ```vue= <MyComponent> <!-- ここに何かを入れたくなった時に使う --> </MyComponent> ``` - [コンポーネントの基本 — Vue.js #スロットによるコンテンツ配信](https://jp.vuejs.org/v2/guide/components.html#%E3%82%B9%E3%83%AD%E3%83%83%E3%83%88%E3%81%AB%E3%82%88%E3%82%8B%E3%82%B3%E3%83%B3%E3%83%86%E3%83%B3%E3%83%84%E9%85%8D%E4%BF%A1) と、[スロット — Vue.js](https://jp.vuejs.org/v2/guide/components-slots.html) の「名前つきスロット」までを読んでください - > 「こういうことができるんだな」くらいのことが分かれば大丈夫です <br> ### ライフサイクルフック - コンポーネントが描画されたらすぐにこれを実行する、などの処理を記述できます - [インスタンスライフサイクルフック](https://jp.vuejs.org/v2/guide/instance.html#%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3%82%B9%E3%83%A9%E3%82%A4%E3%83%95%E3%82%B5%E3%82%A4%E3%82%AF%E3%83%AB%E3%83%95%E3%83%83%E3%82%AF) と、その下の「ライフサイクルダイアグラム」までを読んでください - create: ビューの生成 - mount: ビューの描画 - update: ビューの更新 - destroy: ビューの破壊 (消滅) - 実装例: [ライフサイクルフック · yto60/vue-example@a4f83a0](https://github.com/yto60/vue-example/commit/a4f83a0e85fe20c74146a746f9b938b0d5c86f45) - これを手元で実行して、一番上の「Home」と「About」ページを行き来してみると、以下がコンソールに出力されるはずです - 最初にHomeが表示された時に 'created' - カウンターを増やすボタンを押したり名前欄に入力したり朝昼夜を選択したりする度に 'updated' - HomeからAboutに切り替える時に 'beforeDestroy' - > これ単体では何に使うの? という感じですが、下のAPIリクエストと組み合わせることが多いです <br> ## axios - APIリクエストを飛ばすために使うライブラリ - [axios を利用した API の使用 — Vue.js](https://jp.vuejs.org/v2/cookbook/using-axios-to-consume-apis.html) を読んでください ### 基本的な書き方 ```typescript= import { Vue, Component, Prop, Emit, Watch } from 'vue-property-decorator' import axios from 'axios' @Component({ components: { // ... } }) export default class (コンポーネント名) extends Vue { async (関数名)(){ await axios .get( // (APIリクエストを送るURLの文字列) ) .then(res => { // レスポンスを使った処理 // レスポンスに入っているデータは res.data で取り出せる }) } } ``` ### 例 #### ==実装例1== > まずは型のことをあまり気にせず、axiosを使ってAPIリクエストを送ることだけを考えます [axiosによるAPIの使用 · yto60/vue-example@5fabdae](https://github.com/yto60/vue-example/commit/5fabdae8b3383d1451684610d8a25ef07534a132) 「ユーザー情報を取得」ボタンを押すとサーバーにユーザー情報を問い合わせて、返ってきた結果を表示する実装例です - 呼び出しているAPIの中身は [API仕様](https://app.swaggerhub.com/apis/60-deg/hitonome-API/1.0.0#/) の 「GET /users/me」をクリックするか、ターミナルで `curl -X GET https://virtserver.swaggerhub.com/60-deg/hitonome-API/1.0.0/users/me` を実行すると見ることができます - 65行目で `v-show="me"` ではなく `v-if="me"` を使っているのは、 `me` の初期値がnullで、`me.nickname` 等の呼び出しが行えないからです - 試しに `v-show` に変更してみるとエラーになります コンソールを見ると、 ``` > Object { data: {…}, status: 200, statusText: "OK", headers: {…}, config: {…}, request: XMLHttpRequest } > updated > getUserMe end ``` の順で並んでいると思います > 以下はTypeScript (JavaScript) の非同期処理についての説明なので既に知っている場合は飛ばして実装例2に進んでも大丈夫です 138〜141行目で `axios.get('URL')` を実行してから、実際にサーバーからデータが返ってくるまでの間には、一定の待ち時間が発生します `async`関数の中に`await (待ち時間が発生する処理)`がある場合、待ち時間が発生する処理が終わるまで、`async`関数の実行が一時的に停止されます - `.then()` には、待ち時間が発生する処理が完了してすぐに実行される処理を関数として渡します - 142行目の`res` には、APIリクエストのレスポンスが入っています - `.catch()` はエラー処理です したがって、APIリクエストが正しく返ってきた場合の実行順序は > `axios.get(...)` → `.then(...)` : resの中身が出力され、ビューが更新される → `console.log('getUserMe end')` になります。 138行目の `await` を消してみると、コンソールの出力は下の順序になります ``` > getUserMe end > Object { data: {…}, status: 200, statusText: "OK", headers: {…}, config: {…}, request: XMLHttpRequest } > updated ``` `axios.get(...)` を待つための停止時間がないため、先に `console.log('getUserMe')` が実行されていることがわかります 非同期処理についてのちゃんとした説明は下を参照 - [async function - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/async_function) - [await - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/await) - [Promise - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise) > APIリクエストを送る場合は常にasync / await を使わないといけない、というわけでは無いので、必要に応じて使い分けてください #### ==実装例2== ライフサイクルフックの中での呼び出し これは上ができていれば簡単で、ライフサイクルフックの関数の中で `getUserMe` を呼び出すだけです ```typescript= async mounted() { // APIリクエストを送る await this.getUserMe() } ``` これで、ユーザーがボタンを押すのを待つことなく、ページが開いてすぐにAPIリクエストが送信され、ユーザー情報が表示されます - [axiosによるAPIの使用 (mounted内で呼び出し) · yto60/vue-example@40abb84](https://github.com/yto60/vue-example/commit/40abb84410e9694fff369a7a8c0696a192adcffa) #### ==実装例3== 型をきちんと定義する [axiosによるAPIの使用 (AxiosResponseを使ってresponseの型を定義) · yto60/vue-example@a270baa](https://github.com/yto60/vue-example/commit/a270baaf67562947ac6df093be45507488fb5559) - 例1では `me: any = null` (115行目) としていますが、これでは型チェックの恩恵を受けることができないため、あまり良くないです - たとえば、スクリプト中の他の箇所で `me.nickname` と間違えて `me.name` を呼び出そうとした場合、例1ではこの間違いを検出することができません - 上の例3でそれをやろうとしたらちゃんとコンパイラが気づいて怒ってくれます #### ==実装例4== POSTリクエストを送る [axiosによるAPIの使用 (POSTリクエスト) · yto60/vue-example@94c3d36](https://github.com/yto60/vue-example/commit/94c3d36649cf8c473c5c36aaf2649d11f55c500f) - Groupsインターフェースの`id?: string` などは任意のプロパティです - 参考: [インターフェース | TypeScript 日本語ハンドブック | js STUDIO #任意のプロパティ](http://js.studio-kingdom.com/typescript/handbook/interfaces#optional_properties) - URLがさっきまでと違うのは、実際に送ったリクエストの中身を見てみるため - https://beeceptor.com/console/tofu-manju-test を開いてから実装例のPOSTリクエストを送信すると、その中身を見ることができます - 送信したユーザー名のデータがちゃんと入っていることを確認してみてください - リクエストしたグループ名とレスポンスに入っているグループ名が違うのは仕様です - モックサーバーなのでリクエストの中身に関わらず同じレスポンスを返します #### ==実装例5== > axios.default.baseURLの設定 [axiosによるAPIの使用 (defaultURLを設定) · yto60/vue-example@6cbcec1](https://github.com/yto60/vue-example/commit/6cbcec1311523de686bade535335cc54ca81b537) - URLを全部置換することなくモックサーバーと本来のサーバーを切り替えられて嬉しい ## TypeScriptの使用 ### TypeScript概要 - 概要: [まずは5分で学ぶ!TypeScriptことはじめ | Developers.IO](https://dev.classmethod.jp/client-side/javascript/typescript-tutorial/) - チートシート: [TypeScript cheatsheet](https://devhints.io/typescript) - 詳しく: - [基本の型 | TypeScript 日本語ハンドブック | js STUDIO](http://js.studio-kingdom.com/typescript/handbook/basic_types) - [TypeScriptの型システム](https://typescript-jp.gitbook.io/deep-dive/type-system) - [TypeScriptの型入門](https://qiita.com/uhyo/items/e2fdef2d3236b9bfe74a) - その他 - [npm module に typescript の型定義がない時に、とりあえずビルドが通るようにする](https://medium.com/@ryutamaki/npm-module-%E3%81%AB-typescript-%E3%81%AE%E5%9E%8B%E5%AE%9A%E7%BE%A9%E3%81%8C%E3%81%AA%E3%81%84%E6%99%82%E3%81%AB-%E3%81%A8%E3%82%8A%E3%81%82%E3%81%88%E3%81%9A%E3%83%93%E3%83%AB%E3%83%89%E3%81%8C%E9%80%9A%E3%82%8B%E3%82%88%E3%81%86%E3%81%AB%E3%81%99%E3%82%8B-fcc090804b21) - [TypeScript interfaceの拡張あれこれ](https://qiita.com/pentamania/items/200e4c09285c01f8917f) ### Vue.js + TypeScript - [TypeScript のサポート (公式ドキュメント)](https://jp.vuejs.org/v2/guide/typescript.html) Vue.js公式サイトの例は全部jsで書かれているので、そのままコピペしても動かない場合があります 下を参考にして書き換えてください - [kaorun343/vue-property-decorator: Vue.js and Property Decorator](https://github.com/kaorun343/vue-property-decorator): 公式 - [Vue.js to TypeScriptの書き方一覧 - Qiita](https://qiita.com/ryo2132/items/4d43209ea89ad1297426) <details> <summary>備考</summary> 本来、TypeScriptはJavaScriptの完全な上位集合 (super set) なので、JavaScriptのコードをTypeScriptのコンパイラによってコンパイルした場合は元のJavaScriptコードが生成されます (型エラーがない場合) <br> JSとTSで書き方が異なるのはVueの都合によるものです </details> #### 超簡易チートシート ```typescript= <script lang="ts"> import { Component, Prop, Emit, Watch, Vue } from 'vue-property-decorator' import ChildA from '@/components/ChildA.vue' import ChildB from '@/components/ChildB.vue' @Component({ components: { ChildA, ChildB } }) export default class HelloVue extends Vue { /** props */ @Prop({ type: String, required: true }) readonly val!: string /** data */ value: string = this.val inputValue: string = '' /** emit */ @Emit('handle-click') clickButton(val: string): void {} /** watch */ @Watch('value') onValueChange(newValue: string, oldValue: string): void { console.log(`watch: ${newValue}, ${oldValue}`) } /** computed */ get isDisabled(): boolean { return this.inputValue === '' } /** lifecycle hook */ mounted(): void { console.log('mounted') } /** methods */ handleInput($event: Event): void { this.inputValue = (($event.target as any) as HTMLInputElement).value } handleClick(): void { if (this.inputValue === '') { return } this.value = this.inputValue this.inputValue = '' this.clickButton(this.value) } } </script> ``` ## ルーティング [Vue Router 公式ドキュメント](https://router.vuejs.org/ja/) `src/router` 以下で定義します WIP ## Vuex [Vuex 公式ドキュメント](https://vuex.vuejs.org/ja/) コンポーネントが多くなると、毎回propsとemitでデータのやり取りをするのは面倒 全コンポーネントで共有したいデータを管理するためにVuexを使う そんなに大きくないアプリケーションなら無くてもどうにかなるので、今回使うかどうかは迷っています WIP