質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.72%

Vueの単一ファイルコンポーネントで、データの受け渡しができない

解決済

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 2,816

moke

score 2177

前提・実現したいこと

Rails 5.1で作るVue.jsアプリケーションを参考にRails5.1 + webpack + vue.jsでアプリケーションを作成しているのですが。
Rails5.1の正式リリースから単一ファイルのコンポーネントの使用がデフォルトになったため,うまく動かないようなので、
上記内容を単一ファイルのコンポーネントで書き換えたいと思っています。

発生している問題・エラーメッセージ

Error in data(): "TypeError: Cannot read property 'forEach' of undefined"


①の部分を消した場合。

[Vue warn]: Unknown custom element: <demo-grid> - did you register the component correctly? For recursive components, make sure to provide the "name" option.

該当のソースコード

app.js

import Vue from 'vue'
import Vuex from 'vuex'
import App from './app.vue'
import DemoGrid from './demo-grid.vue'
//import 'vue-turbolinks'
//window.addEventListener('turbolinks:load', function(){
Vue.use(Vuex);
document.addEventListener('DOMContentLoaded',function(){
new Vue(App).$mount('#demo');  
new Vue(DemoGrid).$mount('demo-grid');
});


app.vue

<template>
<div id="demo">
<form id="search">
Search <input name="query" v-model="searchQuery">
</form>
<demo-grid 
v-bind:data="gridData" 
v-bind:columns="gridColumns" 
v-bind:filter-key="searchQuery">
</demo-grid>
</div>
</template>

<script>
module.exports = {
    el: '#demo',
  data: {
    searchQuery: '',
    gridColumns: ['name', 'power'],
    gridData: [
      { name: 'Chuck Norris', power: Infinity },
      { name: 'Bruce Lee', power: 9000 },
      { name: 'Jackie Chan', power: 7000 },
      { name: 'Jet Li', power: 8000 }
    ]
  }
};

</script>

<style scoped>

</style>


demo-grid.vue

<template>
     <table>
    <thead>
    <tr>
      <th v-for="key in columns"
          @click="sortBy(key)"
          :class="{ active: sortKey == key }">
        {{ key | capitalize }}
        <span class="arrow" :class="sortOrders[key] > 0 ? 'asc' : 'dsc'">
          </span>
      </th>
    </tr>
    </thead>
    <tbody>
    <tr v-for="entry in filteredData">
      <td v-for="key in columns">
        {{entry[key]}}
      </td>
    </tr>
    </tbody>
  </table>
</template>    
<script>
module.exports = {
  props: {
    data: Array,
    columns: Array,
    filterKey: String
  },
  data: function () {
    var sortOrders = {}
    this.columns.forEach(function (key) {//①ここを抜くと値のないdemo-gridがhtmlとして描画される。
      sortOrders[key] = 1
    })
    return {
      sortKey: '',
      sortOrders: sortOrders
    }
  },
  computed: {
    filteredData: function () {
      var sortKey = this.sortKey
      var filterKey = this.filterKey && this.filterKey.toLowerCase()
      var order = this.sortOrders[sortKey] || 1
      var data = this.data
      if (filterKey) {
        data = data.filter(function (row) {
          return Object.keys(row).some(function (key) {
            return String(row[key]).toLowerCase().indexOf(filterKey) > -1
          })
        })
      }
      if (sortKey) {
        data = data.slice().sort(function (a, b) {
          a = a[sortKey]
          b = b[sortKey]
          return (a === b ? 0 : a > b ? 1 : -1) * order
        })
      }
      return data
    }
  },
  filters: {
    capitalize: function (str) {
      return str.charAt(0).toUpperCase() + str.slice(1)
    }
  },
  methods: {
    sortBy: function (key) {
      this.sortKey = key
      this.sortOrders[key] = this.sortOrders[key] * -1
    }
  }
};

</script>

<style scoped>
body {
font-family: Helvetica Neue, Arial, sans-serif;
font-size: 14px;
color: #444;
}

table {
border: 2px solid #42b983;
border-radius: 3px;
background-color: #fff;
}

th {
background-color: #42b983;
color: rgba(255,255,255,0.66);
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}

td {
background-color: #f9f9f9;
}

th, td {
min-width: 120px;
padding: 10px 20px;
}

th.active {
color: #fff;
}

th.active .arrow {
opacity: 1;
}

.arrow {
display: inline-block;
vertical-align: middle;
width: 0;
height: 0;
margin-left: 5px;
opacity: 0.66;
}

.arrow.asc {
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-bottom: 4px solid #fff;
}

.arrow.dsc {
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 4px solid #fff;
}
</style>

試したこと

とりあえず、vue公式を一通り読んで、下記のサイトを参考に色々、置き換えて見ましたが、データの受け渡しが全くうまくいっていないようです。
一応参考にしたサイトたち
単一ファイルのコンポーネント間通信
vue.jsのcomponentをwebpackで.vueにして単一ファイルコンポーネントにする
Vue.js初心者向け:インラインテンプレートから単一ファイルコンポーネントの使い方まで

補足情報(言語/FW/ツール等のバージョンなど)

rails5.1.1
vue.js 2.3.3

方針やヒントだけでも構わないので、情報をいただけると嬉しいです。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

check解決した方法

0

自己解決しました。
単一ファイルコンポーネントを親子にして
読み込ませるだけです。
turbolinksとの相性もいいですし、Rails5.1軽くて素直なvueで決まりです。

app.vue

<template>
<div id="demo">
<form id="search">
Search <input name="query" v-model="searchQuery">
</form>
<demo-grid
:data="gridData" 
:columns="gridColumns" 
:filter-key="searchQuery">
</demo-grid>
</div>
</template>

<script>
import DemoGrid from './demo-grid.vue';    
export default {
  el: '#demo', 
  data: {
    searchQuery: '',
    gridColumns: ['name', 'power'],
    gridData: [
      { name: 'Chuck Norris', power: Infinity },
      { name: 'Bruce Lee', power: 9000 },
      { name: 'Jackie Chan', power: 7000 },
      { name: 'Jet Li', power: 8000 }
    ]
  },
  components: {DemoGrid: DemoGrid},
}
</script>

<style scoped>

</style>


demo-grid.vue

<template>
     <table>
    <thead>
    <tr>
      <th v-for="key in columns"
          @click="sortBy(key)"
          :class="{ active: sortKey == key }">
        {{ key | capitalize }}
        <span class="arrow" :class="sortOrders[key] > 0 ? 'asc' : 'dsc'">
          </span>
      </th>
    </tr>
    </thead>
    <tbody>
    <tr v-for="entry in filteredData">
      <td v-for="key in columns">
        {{entry[key]}}
      </td>
    </tr>
    </tbody>
  </table>
</template>    
<script>
export default {
  props: {
    data: Array,
    columns: Array,
    filterKey: String
  },
  data: function () {
    var sortOrders = {}

    return {
      sortKey: '',
      sortOrders: sortOrders
    }
  },
  computed: {
    filteredData: function () {
      var sortKey = this.sortKey
      var filterKey = this.filterKey && this.filterKey.toLowerCase()
      var order = this.sortOrders[sortKey] || 1
      var data = this.data
      if (filterKey) {
        data = data.filter(function (row) {
          return Object.keys(row).some(function (key) {
            return String(row[key]).toLowerCase().indexOf(filterKey) > -1
          })
        })
      }
      if (sortKey) {
        data = data.slice().sort(function (a, b) {
          a = a[sortKey]
          b = b[sortKey]
          return (a === b ? 0 : a > b ? 1 : -1) * order
        })
      }
      return data
    }
  },
  filters: {
    capitalize: function (str) {
      return str.charAt(0).toUpperCase() + str.slice(1)
    }
  },
  methods: {
    sortBy: function (key) {
      this.sortKey = key
      this.sortOrders[key] = this.sortOrders[key] * -1
    }
  }
};

</script>

<style scoped>
body {
font-family: Helvetica Neue, Arial, sans-serif;
font-size: 14px;
color: #444;
}

table {
border: 2px solid #42b983;
border-radius: 3px;
background-color: #fff;
}

th {
background-color: #42b983;
color: rgba(255,255,255,0.66);
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}

td {
background-color: #f9f9f9;
}

th, td {
min-width: 120px;
padding: 10px 20px;
}

th.active {
color: #fff;
}

th.active .arrow {
opacity: 1;
}

.arrow {
display: inline-block;
vertical-align: middle;
width: 0;
height: 0;
margin-left: 5px;
opacity: 0.66;
}

.arrow.asc {
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-bottom: 4px solid #fff;
}

.arrow.dsc {
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 4px solid #fff;
}
</style>


app.js

import Vue from 'vue'
import Vuex from 'vuex'
import App from './app.vue'
import DemoGrid from './demo-grid.vue'
Vue.use(Vuex);
document.addEventListener('DOMContentLoaded',function(){
// bootstrap the demo
var cont = new Vue(App).$mount('#demo');  
});

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.72%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる