誰も書かないような凡ミスをあえて書く
Vue.jsめっちゃ好きなんですけど、最初の頃はググってもよくわからない変なミスでハマることがあったので、あえてそれを書いていこうと思います。
ちなみにこの記事は私がハマったVue.jsのショボいミス - GitPitch というLTで発表した内容を、整理した内容です。
なんかよくわからんけど動かない(2019/04/22追記)
ホットリロードがあるから大丈夫!と全面的に信用しないでください。
リロードしたら直ることがあります。
細かい理由は後述。
ちょっといじったら突然画面いっぱいにエラーが表示された
デカいわ黒背景の白文字だわでビビりますが、落ち着いてメッセージを呼んでください。
このオーバーレイはコンパイルエラーなので、どこかに間違いがあるはずです。
そして、だいたいの場合はどこがエラーの原因なのかもちゃんと書いてあります。
タグを足したらエラーになった
タグを追加する場所が悪いとそうなります。
<template>
<div class="hello">
</div>
<div>
</div>
</template>
Failed to compile.
./node_modules/vue-loader/lib/template-compiler?{"id":"data-v-469af010","hasScoped":true,"transformToRequire":{"video":["src","poster"],"source":"src","img":"src","image":"xlink:href"},"buble":{"transforms":{}}}!./node_modules/vue-loader/lib/selector.js?type=template&index=0!./src/components/HelloWorld.vue
(Emitted value instead of an instance of Error)
Error compiling template:
<div class="hello">
</div>
<div>
</div>
- Component template should contain exactly one root element. If you are using v-if on multiple elements, use v-else-if to chain them instead.
@ ./src/components/HelloWorld.vue 11:0-366
@ ./src/router/index.js
@ ./src/main.js
@ multi (webpack)-dev-server/client?http://localhost:8081 webpack/hot/dev-server ./src/main.js
注目すべきは Component template should contain exactly one root element. If you are using v-if on multiple elements, use v-else-if to chain them instead.
このメッセージですね。
ルートエレメントは1個でなければなりません。
「Main」コンポーネントがエラーになる
Mainコンポーネントは作れません。
なぜならHTML5に<main>タグがあるからです!
[Vue warn]: Do not use built-in or reserved HTML elements as component id: main
はい、メッセージにちゃんと予約されたエレメントですって出ますね。
(私がハマった時はこのメッセージではなかったような?)
同様の理由で、head, header, footer などのコンポーネントも作れないので気をつけてください。
<script>
export default {
name: "main" //←アウト *Main.vueでnameを省略もアウト
}
</script>
そもそも論、Vue.jsのスタイルガイドに コンポーネントの名前は2単語の組み合わせにしなければなりません
というものがあるので、そちらを守ればこんなことにはならないのですけど。
絶対にあるはずのprops受け取り/子コンポーネントが呼び出しがうまくいかない
「prop」「component」って書いてたとか、やらかしました。これらは複数形にすべきで、props, components が正しいですね。
よくよく考えたら配列やオブジェクトとして受け取りますし。単数ではなく複数形です。
地味になんのメッセージも出ないので、一度ハマるとなかなか気づけない罠ポイントかなと思っています。
コンポーネントのマウントがうまくいかない(2019/04/22追記)
import したコンポーネントのオブジェクトをそのまま渡してください。
うっかり文字列で渡したりするとうまくマウントできません
<script>
import Hoge from '~/Hoge.vue'
export default{
// components: [ 'Hoge' ] ←色々間違い
components: { Hoge } // ← importしたオブジェクトをマウント
}
</script>
絶対にあるはずのメソッドがない
前述の「method」にしてたパターンに加えて、ネストが間違ってて methods の下にいなかったことがあります。
絶対にあるはずメソッドetcがないその2(2019/04/22追記)
JavaScriptの監視機構の都合で新しいキーを追加した場合やファイルを追加した場合、そのことを検知できないことがあります。
なので、Vueクラスに初めてmethodsを追加した場合などは
おそらくmethodsが生えていることを感知できていない可能性があります。
ということでリロードしてみましょう
全部アロー関数にしたらハマった
ES6系のシンタックス紹介記事で「どんどんアロー関数に置き換えよう」「普通のfunction() 定義とかもういらないし」みたいなノリの文章とかあるじゃないですか。
だもんで私もやってみたんですよ。
次のコードは、isShowの状態を切り替えて、v-ifを表示したり消したりしたいコンポーネントです。
<template>
<div class="container">
<button v-on:click="toggelShow">ボタン</button>
<p v-if="isShow">見えてる</p>
</div>
</template>
<script>
export default {
//↓多分危険
data: () => {
return {
isShow : true
}
},
methods : {
//↓アウト
toggelShow: () => {
this.isShow = !this.isShow;
}
},
}
</script>
実はこのコンポーネント、うまく動きません。
というのも、 toggleShow をアロー関数にしたためthis = 親コンポーネントとか、windowとか指します!thisをこのコンポーネントに束縛していないのです!!
data に関しても、thisを束縛していないので、propsで受け取った値をthis経由で参照しようとすると失敗するんじゃないかと思います。
解消法は二通り。(下記のコード
<script>
export default {
//↓ES5までの記法
data: function() => {
return {
isShow : true
}
},
methods : {
//↓オブジェクトのメソッド記法
toggelShow() {
this.isShow = !this.isShow;
}
},
}
</script>
従来どおりの「プロパティは関数ですよ」と定義するか、ES6シンタックスの「オブジェクトのメソッドですよ」と定義してあげればOKです。
アロー関数の使い所は各種コールバックに使う、ですね。
axiosやlodashのメソッド群のコールバックに使うとめっちゃ気持ちいいです。
ESLintがとんでもなくうざかった/ESLintを上手に活用したい
最近initしたプロジェクトではそんなこともなさそうですが、
昔はルール違反なコードを書くとオーバーレイが全面を覆ってとんでもなかったです。
// Use Eslint Loader?
// If true, your code will be linted during bundling and
// linting errors and warnings will be shown in the console.
useEslint: true, // ← falseにしましょう
// If true, eslint errors and warnings will also be shown in the error overlay
// in the browser.
showEslintErrorsInOverlay: false, // ← デフォルトtrueだったのかなぁ
ESLintにコードを修正してもらうには、
package.json の scriptsの lint
をチェックして、 --fix
を加えたscriptを作りましょう。
gitのpre commit hookとかに仕込むとさらによさげです。
scripts: {
//前略
"lint": "eslint --ext .js,.vue src ",
"fix" : "eslint --fix --ext .js,.vue src"
//後略
}
配列に値を追加したけど画面に反映されない(2020/04/12追記)
配列のキーを指定して直接値を追加する方法で反映されないことがあります。
data(){
return {
arr: [1,2,3]
}
},
methods: {
add(value, index){
this.arr[index] = value // add(4, 4)しても反映されない……と思う
}
}
反映される場合とされない場合があって、
それは 最初から定義されているキー
及び Array.push()などで追加されたキー
なら代入で反映されます。
未定義(未監視)のキー
に対しての値の代入操作では画面に反映されません。
また、反映されるけどなんか遅い……みたいなことが起こります。
配列への代入に際して、インデックスを指定して代入したいときは、代わりにVue.$set()
を使いましょう。
コンポーネント内で使うときは、Vue.$set()
の代わりにthis.$set()
になります
methods: {
add(value, index){
this.$set(this.arr, index, value)
}
}
添え字指定の代入操作が遅延するのは、Vueのリアクティブシステムの監視機構に引っかからないためです。
監視機構に対応したArrayの拡張関数はpush()
,pop()
,shift()
,unshift()
,
splice()
,sort()
,reverse()
です。
オブジェクトに値を追加したいけど反映されない(2020/04/13)
配列の添字指定と似たような話なのですが、オブジェクトに値を追加したいけど反映されないことがあります。
やはり反映される場合とされない場合があって、すでに存在するキーへの代入は即座に反映されますが、
存在しないキーへの代入操作は一切反応しません。
data(){
return {
obj: {
name: "foo"
}
}
},
methods: {
setName(value){
this.obj.name = value; // 反映される
},
setLocation(value){
this.obj.location = value; // 反映されない
}
}
こちらもVueの監視機構の都合でして、
存在しないキーをオブジェクトに追加する場合はVue.$set()
を使う必要があります。
Vueのコンポーネント内ではthis.$set()
でよいです。
methods: {
setLocation(value){
this.$set(this.obj, "location", value);
}
}
第2引数の文字列はもちろん変数でもよいので、動的なキーにもできます。
ちなみにキーさえあればよいので、代入予定のあるキーは
予めdata()で生成する時点で値はnullにして作成しておくと、this.$set()を使わなくてよくなります。
なお、オブジェクトをまるごと差し替えてしまうとこれもVueの監視機構にひっかからなくなってしまいます。
これは、data()の中で作られていないオブジェクトはVueの監視対象外だからです。
オブジェクトをマージしたい場合は、以下のような操作をする必要があります
this.userProfile = Object.assign({}, this.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
})
詳しくはこちらをご覧ください。
Vueの{{}}を別のシンボルに置き換えたい(2019/04/22追記)
テンプレートでの変数展開公文 {{}}
mastacheですが、これは変更可能です。
もし、テンプレートエンジンの変数展開シンタックスと被った場合は、
変更を考慮してもよいかもしれません。
(言うまでもありませんが、テンプレートエンジンの変数展開シンタックスとどちらのシンボルを置き換えるかはプロジェクトによって最適な選択をしてください)
ストアを追加したが読み込まない(2019/04/22追記)
前述の監視機構の都合の話の追加です。
storeを途中から追加した場合、そのファイルが読み込まれていないことがあります。
こちらはwebpack上のファイル監視対象から漏れている可能性があります。
この場合、npm run dev(npm run hot)などをやり直すことで初めて読み込まれるようになるので、気をつけてください。
mapXXX から値が参照できない(2019/04/22追記)
コンポーネントでmapXXXX()を使って参照する時の引数はString Arrayです。
うっかり参照したいものが1個だからといってStringを渡したり、あるいは複数を参照したいからとString引数2個を渡したりしないようにしてください。呼び出せません。
dispatch, mutationの時にpayloadの引き渡しがうまくいかない(2019/04/22追記)
受け取りの型をオブジェクトにしてたら渡す時の型もオブジェクトにして引数を揃えてください。
型が合ってないと値が渡せません
(余談)サーバーサイドプロジェクトから見たVue.jsとの付き合い方(2019/04/22追記)
- SPAを考えるならVue(CLI)/NuxtのプロジェクトとAPIサーバーのプロジェクトの2つに分けるほうがだいたい楽
- サーバーサイドのルーティングとSPA(フロントのルーティング)が両方あると世界が崩壊するのでやってはいけない
- SPAでOGPやSEOを考えるならSSRができるNuxtのUniversalモードを選ぶべき
- 中規模開発くらいになると色々規約に乗っかれて開発できるNuxtが楽だと思う
- 小規模開発かつOGPやSEOを気にしなくていいならVueCLIで構築したプロジェクトのほうが考えること少なくて楽かも
- 中規模か小規模かの分水嶺は個人的にはVuex使かどうかだと思っていて、ページをまたいで状態を管理したいならNuxtでいいんじゃない?って思っている(諸説あります)
↑↑↑ここまでSPA開発の話↑↑↑
↓↓↓ここからテンプレートエンジンでHTMLをレンダリングする世界の話↓↓↓
- サーバーサイド言語のテンプレートエンジンに部分的にVueを導入することもできる(むしろチュートリアルではこの方法で紹介している)
- ごく小規模なら単一ファイルコンポーネント(.vueファイル)を使わない開発の方が小回りが効いていいかも
- もしくはLaravelMixで単一ファイルコンポーネントとテンプレートエンジンの世界を両立する
- 別途CLIで作ったプロジェクトを作って、buildした結果をサーバーサイドプロジェクトに展開して利用するという方法はありかもなぁ
- vue-routerを使うときはよく考えること
まとめ
以上が私の記憶の限りのハマりどころになります。
ううーん、始めた頃の記憶をもうあんまり覚えてない。
ということで、「こんなところにハマったよ!」などなどあればコメントいただければ嬉しいなと思います。