【Vue起動】
cdコマンドでVueプロジェクトに移動
npm run serve
■バージョン確認
・Node.js
node -v
・npm
npm -v
・Vue
npm list vue //プロジェクトにインストールしている場合 npm list -g vue //グローバルにインストールしている場合
・VueCLI
vue -V
・2021/10時点での最新の安定バージョン
Vue: 2.6.14
Vue/cli: 4.5.13
・TypeScript
tsc -v
【Vue.jsの特徴】
- 双方向バインディングでリアルタイムに値の更新が可能
- コンポーネントで部品化し、複数のコンポーネントを組み合わせることで柔軟にviewを生成する
- SPAを作るのに適している
- 単一ファイルコンポーネントで作成する(HTML、js、cssを1つのファイルに記述する)
【Vueプロジェクトの基本】
- viewフォルダにはテンプレートを、componentsフォルダには再利用可能なコンポーネントを作成する
- App.vueでアプリケーションを定義し、そこからviewのHome.vueを呼び出す。
- Home.vueからcomponentsフォルダのコンポーネントを呼び出して使用する。
App.vue(ルート)
┗Home.vue(viewフォルダ)
┗Comp.vue(componentsフォルダ)
たとえば、Header.vueならApp自体の構成になるのでApp.vue直下に置く。
App.vueの
デフォルトでは『/』をHomeと読みかえる記述となっているため、viewフォルダのHome.vueが読み込まれる。
- CSSは遷移元のCSSを引き継ぐため、複数のビューで同じCSSクラス名を使用していると意図しない装飾が反映される。命名規則などによる明確な定義が必要
~App.vue~
<temeplate> <div> //vueでは1コンポーネントしかk返さない約束なので、template内は1つのdiv等にまとめる <Questions></Questions> //@Componentしたファイルを要素として呼び出せる </div> </template> <script lang="ts"> import { Component, Vue } from 'vue-property-decorator' //使用するアノテーションを定義 import Question from './components/question.vue' //コンポーネントとして使用するファイルをimportして名前を付ける @Component({ components: { Questions //importしたファイルをHTMLの要素として登録 } }) export default class App extends Vue { //設定したjsを記載する } </script>
【v-bindとv-on】
<input type="text" :value = "clientNameChild" //:valueはv-bind:valueの省略形 //子にデータを渡す場合に記述する。自身のコンポーネントのみで使用する場合は不要 //inputのvalue(入力された値)を子コンポーネントのclientNameChildプロパティに代入 @input = "clientName = $event.target.value" //@input=v-on:inputと同義 //inputを監視して自身のclientNameプロパティを更新する >
【v-model】
v-modelはv-bindとv-onをまとめたもの。
フォームアイテムによってv-bindとv-onの展開のされ方は異なる
アイテム: v-bind/v-on
テキストボックス、テキストエリア: value/input
チェックボックス、ラジオボタン: checked/change
セレクトボックス: value/change
【v-for】
参考:
【徹底解説】これを見ればわかるvue.jsのv-forのkeyの動作 | アールエフェクト
<div v-for="category in categories" v-bind:key="category.id"> <Category :category = "category.text" //:categoryはv-bind:category の省略形 :category1Desc = "category.desc" /> </div>
※文字列と組み合わせたい場合には以下
<Category :category1 = "'addText' + category.title" //固定テキストは『'』でくくる />
v-bindは、指定した要素が何らかのリクエストを起こした際にレスポンスをどの要素に返すかを示す。
なのでv-forで複数の要素が生成される際や、inputで値を入力した際に使用される。
・ラジオボタンのバインディング
参考:
Vue.jsでラジオボタン [radio button]
~Question.vue~ ※子コンポーネント
<div v-for="item in selection" v-bind:key="item.index"> <input type="radio" :name="'question' + id" :id="'question' + id + '-item' + item.point" v-bind:value="item.point" //radioのvalueを設定 //省略して:value="item.point"のみでも可 v-on:change="$emit('changed', $event.target.value)" …① //radioなのでeventは"change"を指定する。 //"$emit('関数名', 関数の引数)" 親要素ではこの関数名で呼び出せる //$eventはv-on:changeイベント自身を指す //すなわち、radioボタンが変更(change)されたらその値(target.vlue9を引数に関数が発火する > <label :form="'question' + id + '-item' + item.point"> {{ item.text }} </label> </div>
~Home.vue~
<template> <Question @changed="fire" //①で指定した関数名を感知して"fire"メソッドを発火。引数も同時に受け取っている :id="question.id" :selection = "question.selection" /> </template> <script> fire (val) { //引数、すなわちradioで選択された値を受け取ることができる console.log(val) } selection = [ { text='質問1' poitn=1 }, { text='質問2' poitn=2 } ]
【propsと@Prop】
Vueでは親コンポーネントで子コンポーネントを呼び出す際に、一緒にプロパティを渡すことができる。
子コンポーネントでは、そのプロパティを受け取るためにpropsを使用する。
~親コンポーネント~
<SampleComp :sampleProp="sample" />
~子コンポーネントでの値の受け取り~
・通常の書き方 ※props
<script> export default { props: { userName: { type: String, required: true } } } </script>
・typescriptの書き方 ※@Prop
<script lang="ts"> import { Component, Prop, Vue } from 'vue-property-decorator'; @Componrnt export default class SampleComponrnt extends Vue { @Prop({ type: String, required: true }) userName: string; </script>
■配列をpropsとして渡す
親
items: ['iphone', android]
子
<template> <div v-for="item in items" v-bind:key="item.index"> //配列がidを持っていれば:key="item.id"が理想 <p>{{ item }}</p> </div> </template> <script lang="ts"> @Prop items!: []; //Array<String>;といった渡し方の方が正解? </script>
※divでなくspanで渡す場合はindexof()?
<span v-for="item in items" v-bind:key="item.indexof()"></span>
【Emit】
参考:
propsと$emitでデータを引き渡す - Qiita
■基本
~Child.vue~
<template> <div></div> </template> <script lang="ts"> export default class Category extends Vue { sumChild = 0; function () { this.sumChild += 1; this.$emit('eventName', this.sumChild); …① //this.$emit('親に送信するイベント名', 値) } } </script>
~Parent.vue~
<template> <Child @eventName="sumParent=$event" //@eventNameで①で指定したイベント名を感知 //$eventに子から送信された値が入る /> </template> <script lang="ts"> export default class Category extends Vue { sumParent = 0; } </script>
・直接ではなく関数に値を送信したい場合
~Parent.vue~
<template> <Child @eventName="reflentSum" /> </template> <script lang="ts"> export default class Category extends Vue { reflectSum (e) { //eに子から送信された値が入る console.log(e); } } </script>
・inputのvalueではなく別の値を介したい場合は関数を挟む
~Child.vue~
<input type="●●" v-bind:value="value" v-on:change="changed" > <script lang="ts"> export default class Sample extends Vue { changed (e) { //イベントを任意の引数名でキャッチできる ※今回はeと指定している個所 let newItem = 0; newItem = e.target.value + 1; //何らかの処理 this.$emit('changedNext', newItem) //('関数名', '関数の引数') 親要素ではこの関数名で呼び出し引数を受け取る } } </script>
・watchも使いやすい?
[vue.js]子コンポーネントの値を親コンポーネントで合計したい
■フォームから
子コンポーネントで親に値を送信
~Child.vue~
<input type="●●" v-bind:value="value" //inputのvalueを設定 //省略して:value="value"のみでも可 v-on:change="$emit('changed', $event.target.value)" …① //v-on:イベント名。radioならchange、buttonならclick //"$emit('関数名', 関数の引数)" 親要素ではこの関数名で呼び出せる //$eventはv-on:changeイベント自身を指す //すなわち、radioのボタンが変更(change)されたらその値(target.value)を引数に関数が発火する >
親コンポーネントで受け取り
~Parent.vue~
<template> <Child @change="fire" …①で指定した関数を感知してfireメソッドを発火。①の引数がfireメソッドの引数となる /> </template> <script lang="ts"> export default class Category extends Vue { fire (val) { console.log(val); } } </script>
・直接値を渡したいなら
参考:
Vueのデータバインドの基本 - Qiita
~Parent.vue~
<template> <Child @changed="propName=$event" //子コンポーネントから送られた引数が#eventとしてわたってくる /> </template>
・さらに親となるコンポーネントに値を伝はさせたい
方法1 :Watchで変更を監視
@Watch('propName', { deep: true }) //propName子要素の変更まで感知する場合はdeep: true propNameChangedDeep () { console.log('catch' }
【算出プロパティ】 computed
template内での扱いは通常のプロパティと同様。
プロパティとして使用したいが、複数の値や条件、式から値が算出される場合に使用する
<template> <p>{{ fullName }}</p> <p>{{ sendMessage }}</p> </template> <script lang="ts"> @Prop parentMsg!: string; export default class Home extends Vue { firstName: string = '太郎'; lastName: string = '山田'; get fullName() { //typescriptの場合はget funcName() { とするだけ return `${this.firstName} ${this.parentMsg + 'です' } } </script>
■ゲッターとセッター:getter,setter
private get propName (); <戻り値の型> { return <戻り値> } private set propName (): void { ~更新処理~ }
【画面遷移】viewの切り替え
※パスによるコンポーネントの切り替えであり、URLは変わらない
■参考:
Vue.jsでフォームを使おう - Qiita
■基本
~Home.vue~
<template> <button @click.prevent="validate">回答する</button> </template> <script lang="ts"> ~~~ validate () { this.$router.push(' /result') //<router-link :to="…">と同義 } </script>
~router/index.ts~
{ path: '/result', name: 'Result', component: () => import('../views/Result.vue') props: true //★値を受け渡す場合はこの記述が必要 }
~Result.vue~
■viewを切り替える際の値の受け渡し
上記基本の下、routerの設定でpropsをtrueにしてある前提
以下のようにparamsで指定する。
★パラメータはStringにしないとエラーになる
validate () { this.$router.push({ name: 'Result', params: { param: String(this.param) //自身のparamを遷移再起のparamに渡す } }) }
【バリデーションフィルター】
■参考:
Vue.JSでフォームのvalidationを行う - Qiita
フィルター — Vue.js
単純なフォームバリデーション
フォームのバリデーション — Vue.js
■filtersを使う方法 ※要検証だし@Componentに書かないといけない?
@Component({ filters: validator (clientNmae: string) { if (!clientName) return '' return clientName.charAt(0).toUpperCase() + clientName.slice(1) } })
■VeeValidateライブラリを使用するのがよさそう
Vue.js のフォームバリデーションライブラリ VeeValidate を評価してみた | DevelopersIO
デモあり
VeeValidateでVue.js用の超便利なバリデーションを実装する | カバの樹
【Vue.js】VeeValidateがメジャーバージョンアップで便利になっていた – 株式会社シーポイントラボ | 浜松のシステム・RTK-GNSS開発
古いバージョンとの差異
VeeValidate 2から3へのアップデート - 一休.com Developers Blog
・導入
npm install vee-validate --save
バージョン確認
node_modules/veevalidate/README.meを参照
・import
import { extend, ValidationProvider, ValidationObserver } from 'vee-validate' import { required, email } from 'vee-validate/dist/rules' extend('required', required) //template内でrequiredの名前で使用できるようにする
@Componentに以下を記述
@Component({ components: { ValidationProvider } })
・フォームをValidationProviderで囲う
<ValidationProvider name = "clientName" //ここでしたいしたnameでエラーを呼び出せる rules = "required" v-slot = "{ errors }" //errorsに配列でエラーオブジェクトが格納される :immediate = "true" //即時判定する > <input type="text" name="名前" //エラーメッセージに使用できる value="clientName" v-model="clientName" > <p v-show="errors.length">{{ errors[0] }}></p> //エラーがあったらメッセージを表示 <div> <button @click.prevent="validate(errors)" class="btn btn-basic">次へ</button> </div> </ValidationProvider>
・エラーメッセージのカスタマイズ
extendしている中でカスタマイズする。
extend('required', { ...required, //requiredを継承 message: '※{_field_}は必須項目です' //fieldにはinput内で指定s多name属性の値が代入される })
・フォーム全体にエラーがあるかを監視する:ValidationObserver
<ValidationObserver ref = "observer" v-slot = "{ invalid }" //子のValidationProviderにエラーがある場合invalidがtrueとなる > <ValidationProvider> </ValidationProvider>
★ドキュメントでは以下のような記述が見つかるが廃止されている
v-validate="required" //廃止 rules="required" //OK
※ ValidationProviderをimportする必要があるので注意
【クラスオブジェクトの使用】
クラス定義はexport defaultする前に記載する
<script lang="ts"> import ~~ class Sample { id: number title: string constructor(id: number, title: string) { this.id = id this.title = title } } export default ~~
【Watch】
プロパティの値の変更を監視して、変更があった場合に処理を行う
@Watch('questions', { deep: true} ) //第2引数に設定プロパティを指定できる questionChangeDeep () { console.log('catch'); }
■設定プロパティ
deep: <真偽値> //監視する値がオブジェクトの場合など、ネストされたプロパティの値を監視する
immediate: <真偽値> //初期読み込み時にも実行するか
■複数プロパティの監視
配列にして監視する
//算出プロパティを作成し監視する要素を配列にまとめる get watchTarget () { return [this.param1, this.param2] } @Watch('watchTarget') propChanged () { this.fillData() }
【関数】
@Watch('point') //ラジオボタンなどプロパティを動作検知する場合はWatch @Emit('sumUp') sumUp () { this.sumPoint += this.point }
【mounted】
ライフサイクルフックの一つ。
Vueではインスタンスが作られてから削除されるまでにフックとして処理を挿入できる。
vue.jsのcreatedとmountedの違いを目で見て理解 | アールエフェクト
つまりはVueインスタンスメソッド
【vue-chart.js】
■参考
基本:https://belltree.life/vue-chartjs/
インストール:Vue.js+Chart.jsプロジェクト - Qiita
リアルタイム更新:やたら易しいvue-chart.js解説 | 謎の技術研究部
親子プロパティ連携:Vue.js(ts)でvue-chartjsを使う - Qiita
■基本
・読み込まれる子コンポネントはvue-chartjsによって作られるため
・Propの受け取り
チャートでは子コンポーネントとして作成し、親コンポーネントから読み込むのが一般的な運用。
その中で、chartData、optionsを親コンポーネントから受け取る。
子コンポーネントでは@Propを使用して値を受け取るが、デフォルトの型はvue-chartjsではなくchart.jsから読み込む。
@Component export default ~~ @Prop() public chartData!: Chart.ChartData; @Prop() public options: Chart.ChartData; @Prop() public options!: Chart.ChartOptions;
・vue-property-decoratorのMixisと、vue-chartjsのmixins
・親子コンポネント間のプロパティの共有
vue-chartjsのmixinsオプションのプロパティであるreactivePropを設定するのみ。
これで自動的に監視用のプロパティを作成してwatchしてくれる
<script lang="ts"> import [ Component, Prop, Mixins, Vue ] from 'vue-property-decorator' import Chart from 'chart.js' import [ Radar, mixins ] from 'vue-chartjs' @Component export default class RadarChart extends Mixins(Radar, mixins,.reactiveProp, Vue) { @Prop() public chartData!: Chart.ChartData; @Prop() public options!: Chart.ChartOptions; }
■エラー
・Object is possibly 'undefind'
@Prop
arr = [];
と定義すると、未定義の可能性があります、とのエラー。
arr = Array
・chart_js__WEBPACK_IMPORTED_MODULE_0__.default is not a constructor
Chart.jsは大きなアップデートがあり、vue-chartjsが対応できていない
参考:
vue-chartjsでグラフが表示されない問題 - Qiita
・Property 'extends' has no initializer and is not definitely assigned in the constructor.
vue-chartjsのドキュメントによると、chartTypeを読み込む際に、vueインスタンスのオプションであるextendsに以下のようにアクセスする。
import [ Radar ] from 'vue-chartjs' <script> @Component export default () { extends: Radio } </script>
しかし、この記述方法ではtypescriptを導入している場合、記述仕様上、extendsがプロパティとして認識されてしまいエラーになる。
>Mixinsを利用して以下のように記述する。
<script lang="ts"> import [ Component, Prop, Mixins, Vue ] from 'vue-property-decorator' import [ Radar ] from 'vue-chartjs' @Component export default class RadarChart extends Mixins(Radar, Vue) { }
・Chartjsが見つからない
"export 'default' (imported as 'Chart') was not found in 'chart.js'
参考:
Vue.jsでchart.jsが入らない - Qiita
バージョンの問題らしい。参考をもとにchart.js系のバージョンを指定して入れなおしたら解消した。
【Vue.jsの導入、プロジェクトの開始】
■Vue.jsの導入
参考:
【Vue.js】10分でできる! ~ Vue.jsのインストール → プロジェクト作成と起動まで~ - Qiita
・Node.jsのインストール
Vue.jsはNode.jsの環境を使用したフレームワークであるため、まずはNode.jsのインストールが必要となる。
Node.js
・Node.jsバージョン確認
node --version
これでnpmコマンドが使用できるようになった
・vue-cliのインストール
vueコマンを使用できるようにする
npm install -g @vue/cli //-gでグローバル領域にインストール ※これをしないとパッケージ単位でしかインストールされない
・vue-cliバージョン確認
vue --version
これでvueコマンドが使用できるようになった
■プロジェクトの開始
参考:
vue.js + typescript = vue.ts ことはじめ - Qiita
・プロジェクトの作成
vue create プロジェクト名 Mnually select features を選択
Babel, TypeScript, Router を選択 ※スペースで切り替え
vuexも入れておいた方がよさそう
あとは好みで設定。全部yesでもOK
参考:
【Vue.js】vue-cliを使ってプロジェクトを作成し起動するまでの手順|teamnp|note
Pick a linter はstanderdが無難?
Pick additional lint featuresはLint on save
Where do you prefer placing config //コンフィグの設定場所
Save this as a preset for future projects? //今回の設定を次回も使用できるようにする?
【appendics】
■各コンポーネントはクラスとなる
■プロパティの定義
<script> data : { name: string; }
上記のように記載することで、クラスプロパティとして定義できる。
typescriptの場合はdataの記載は必要なく、以下のようにプロパティのみでよい
<script lang="ts"> name: string;
ちなみに以下のようにすると、型が明らかなので型宣言しないようにとのエラーが出る
name: string = 'sample'; //エラー name = 'sample'; //エラーなし
宣言を明確にしたい場合は2行に分ける
name: string; name = 'sample';
■normalize.css
・インストール
npm install --save normalize.css
・App Vueでimport
<script> import 'normalize.css' export default { name: 'App' } </script>
・確認
デベロッパーツール>Sourcesタブ
以下のパスでnormalize.cssが読み込まれているか
webpack://>.>node_modules>normalize.css
■画像(静的)読み込み
assetsフォルダに配置する
・cssで呼び出す
background-image: url("../assets/img/shadow.png");
■install --save の--saveとは?
以前まではこの--saveオプションをつけないとpackage.jsonのdependenciesに記述されなかった。
現在では、自動で記述されるため不要のはず
■export defaultとvue-property-decorator
export //これで他のファイルからimportできるようになる default //exportするファイルに名前を付けなくても良くなる?※パス指定だけで呼び出せる?
参考:
はじめてのvue-property-decorator (nuxtにも対応) - Qiita
Vue + typescript について (vue-class-component, vue-property-decorator って何?) - Qiita
そもそもVueでTypescriptを導入するために『vue-class-component』を導入している。
これにより、各vueファイルでVueを継承したクラスコンポーネントとして扱うことができる。
この時点ではこのコンポーネントの定義、componentsやpropsを@Componentや、@Optionsの引数内に定義する。
import Vue from 'vue' //Node.jsのファイルからvueの仕様をインポート…① import Component from 'vue-class-component' @Component({ components: ●● //子コンポーネント props: ●● //親から引き継ぐプロパティ }) export default class Sample extends Vue { }
これを使い勝手良くしたものがvue-property-decorator。
上記の①のインポートや、@Component内に記述していたPropsを@Propsとしてクラスコンポーネントのメンバーとして記述できるようにする機能を一手にimportできる。
import { Component, Vue, Prop } from 'vue-property-decorator' @Component({}) export default class Sample extends Vue { }
・vue-property-decoratorのインストール
npm i -S vue-property-decorator
■ページ遷移時にスクロールトップ
/router/index.ts
const router = new VueRouter({ ~~~ scrollBehavior (to, from, savePosition) { if (savePosition) { return savePosition } else { return { x: 0, y: 0 } } } })
■フェードイン
~App.vue~
<template> <transition> //transitionで囲う <router-view></router-view> </transition> </template> .v-enter-active { transition: opacity 0.5s 0.5s; } .v-enter { opacity: 0; } .v-leave-active { transition: opacity 0.5s; } .v-leave-to { opacity: 0; }
※ただし、スクロールトップとか入れていると、消える前にスクロールトップしてしまうので調整が必要
【エラー対応ログ】
■Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value.
親コンポーネントからもらってきたpropsを上書きしないでくれ!
親コンポーネントとの差異が出てしまうよ!
>対処法はEmitの項目を参照
■Property '●●' has no initializer and is not definitely assigned in the constructor.
プロパティ●●が初期化されていないよ!
でも@Propsで親コンポーネントからわたってくる値だから初期化できないが…
そんな時は、確実に親コンポーネントからわたってくる値だよ!を示すアサーションを付ける
@Props() content!: string; //『!』がアサーション。必ず親からプロパティがわたってくることを保証する。 //『?』に置き換えると、わたってこない可能性があることを示す。
■Emit is not defined
定義されていないよ!
import { Component, Prop, Emit, Vue } from 'vue-property-decorator' //ちゃんとEmitをインポートする
■ERR_CONNECTION_TIMED_OUT
Wifiなどネットワーク環境を切り替えると発生する?
ブラウザを再起動したら解消した
■Declaration expected
宣言して!
publicとか書けってこと?
■特定のIPにデプロイしたが
でルーティングしているコンポーネントが表示されない。
以下の2カ所を修正したところ表示された。が、これだとlocalhostでは表示されない…
~vue.config.js~を作成
module.exports = { publicPath: '/AppName/' }
※通常は『./』で相対パスにするが、なぜかコードに反映されない
../AppName/で無理やりやるとコードには反映されるが、相変わらずルーターには認識されない
このときは絶対パスにして解決したが、根本的な解決にはなっていない。
参考になるかも
【Vue.js】historyモードにした際の注意点 | 集の一期一会
■No overload matches this call
Type 'number' is not assignable to type 'string'.
this.$router.pushしようとしたらエラー。
param1はthisでも子コンポーネントでもnumberで一致している。
this.$router.push({ name: 'Result', params: { param1: this.param1 } })
>>パラメータはすべて文字列になる?
参考:
parameters - Vue + Typescript vue-router and type of params - Stack Overflow
param1: String(this.param1)としたらエラーは解消した。
■parameter 'param1' implicity has an 'any' type.
型が定義されていないよ!暗黙的にはanyと思われるよ
以下のように型が未知の引数を指定したときに発生
filters: { varidator (param1) { //ここでエラー if (!param1) return '' return param1 } }
param1: string など型指定してあげる
■ラジオボタンに
Multiple v-models/values inside validation provider gives infinite loop · Issue #2638 · logaretm/vee-validate · GitHub
これによると、入力検地を無効化することができる?ただ、それを可能にするドキュメントはなかった。
vuetifyを使えばいけるのだろうか
■Property 'XXX' does not exist on type 'YYY'
型YYYにはプロパティXXXは存在しないよ
参考:
VueJS with typescriptでしばしば遭遇するProperty 'XXX' does not exist on type 'CombinedVueInstanceの解消法 - Qiita
わかりにくいが、YYYの型が型推論できていないからちゃんと型を定義してね、ということらしい。
特にthisをつかって自身のコンポーネントを指すときになぜか自身の型がわからなくなっちゃうらしい。
厳密にはエラーとなっているYYYの型を調べて as 型 で定義する。
てっとり早い解決策としては as any とする。
例)
エラー個所
export default class Category extends Vue { ~~~ this.questions[i].selectedPoint ~~~ }
エラーメッセージ
Property 'selectedPoint' does not exist on type 'never'.
this.questions[i]が型neverと判定されている。
thisの型をanyにすると解消した。
(this as any).questions[i].selectedPoint
■Duplicate keys detected: '1'. This may cause an update error.
v-forを使用したときのkey重複エラー。
【検索ワード】
Type number trivially inferred from a number literal, remove type annotation
trivially inferred from エラーにしない
★radio v-for v-model
setter
子コンポーネント プロパティ 書き換え
PropTypeとは