LoginSignup
312
287

More than 3 years have passed since last update.

私がハマったVue.jsのショボいミス

Last updated at Posted at 2018-07-11

誰も書かないような凡ミスをあえて書く

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 したコンポーネントのオブジェクトをそのまま渡してください。
うっかり文字列で渡したりするとうまくマウントできません

fuga.vue
<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を表示したり消したりしたいコンポーネントです。

hoge.vue
<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経由で参照しようとすると失敗するんじゃないかと思います。

解消法は二通り。(下記のコード

hoge.vue
<script>
    export default {
        //↓ES5までの記法
        data: function() => {
            return {
                isShow : true
            }
        },
        methods : {
          //↓オブジェクトのメソッド記法
          toggelShow() {
              this.isShow = !this.isShow;
          }
        },
    }
</script>

従来どおりの「プロパティは関数ですよ」と定義するか、ES6シンタックスの「オブジェクトのメソッドですよ」と定義してあげればOKです。

アロー関数の使い所は各種コールバックに使う、ですね。
axiosやlodashのメソッド群のコールバックに使うとめっちゃ気持ちいいです。

ESLintがとんでもなくうざかった/ESLintを上手に活用したい

最近initしたプロジェクトではそんなこともなさそうですが、
昔はルール違反なコードを書くとオーバーレイが全面を覆ってとんでもなかったです。

config/index.js
    // 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とかに仕込むとさらによさげです。

package.json

scripts: {
//前略
    "lint": "eslint --ext .js,.vue src ",
    "fix" : "eslint --fix --ext .js,.vue src"
//後略
}

配列に値を追加したけど画面に反映されない(2020/04/12追記)

配列のキーを指定して直接値を追加する方法で反映されないことがあります。

sample.vue
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()になります

sample.vue
methods: {
  add(value, index){
    this.$set(this.arr, index, value)
  }
}

添え字指定の代入操作が遅延するのは、Vueのリアクティブシステムの監視機構に引っかからないためです。
監視機構に対応したArrayの拡張関数はpush(),pop(),shift(),unshift(),
splice(),sort(),reverse() です。

オブジェクトに値を追加したいけど反映されない(2020/04/13)

配列の添字指定と似たような話なのですが、オブジェクトに値を追加したいけど反映されないことがあります。
やはり反映される場合とされない場合があって、すでに存在するキーへの代入は即座に反映されますが、
存在しないキーへの代入操作は一切反応しません。

sample.vue
data(){
  return {
     obj: {
       name: "foo"
     }
  }
},
methods: {
  setName(value){
     this.obj.name = value; // 反映される
  },
  setLocation(value){
    this.obj.location = value; // 反映されない
  }
}

こちらもVueの監視機構の都合でして、
存在しないキーをオブジェクトに追加する場合はVue.$set()を使う必要があります。
Vueのコンポーネント内ではthis.$set()でよいです。

object.vue
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.js

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を使うときはよく考えること

まとめ

以上が私の記憶の限りのハマりどころになります。
ううーん、始めた頃の記憶をもうあんまり覚えてない。

ということで、「こんなところにハマったよ!」などなどあればコメントいただければ嬉しいなと思います。

こんな記事も書いてます

312
287
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
312
287