Почему vue не может найти переменную в l oop? - PullRequest
2 голосов
/ 11 января 2020

Я изо всех сил пытаюсь разработать простой компонент и использовать его внутри al oop:

<template id="measurement">
    <tr class="d-flex">
    </tr>
</template>
Vue.component('measurement', {
    template: '#measurement',
    props: {
        name: String,
        data: Object,
        val: String,
    },
});

Это, очевидно, еще не функционирует, но уже дает сбой:

<table v-for="(m, idx) in sortedMeters">
    <measurement v-bind:data="m"></measurement>
</table>

дает ReferenceError: Can't find variable: m вид изнутри. По странной причине то же самое работает, то есть без ошибок, в абзаце:

<p v-for="(m, idx) in sortedMeters">
    <measurement v-bind:data="m"></measurement>
</p>

Что приводит к тому, что переменная не найдена?

PS .: вот скрипка: https://jsfiddle.net/andig2/u47gh3w1/. Он показывает другую ошибку, как только включается table.

Обновление Предполагается, что l oop создает несколько таблиц. Строки в таблице будут созданы несколькими measurement s

Ответы [ 3 ]

2 голосов
/ 13 января 2020

TLDR: Прежде чем Vue передается шаблон DOM, браузер выводит <measurement v-bind:name="i" v-bind:data="m"> за пределы <table> (вне контекста v-for), что приводит к ошибкам в Vue. Это известное предостережение при разборе шаблона DOM .


HTML spe c требует, чтобы <table> содержал только определенные c дочерние элементы :

  • <caption>
  • <colgroup>
  • <thead>
  • <tbody>
  • <tr>
  • <tfoot>
  • <script> или <template>, смешанные с указанным выше

Аналогично, модель содержимого <tr> это:

  • <td>
  • <th>
  • <script> или <template>, смешанные с выше

DOM Парсер совместимых браузеров автоматически выводит запрещенные элементы, такие как <measurement>, за пределы таблицы. Это происходит за до стадии сценария (до того, как Vue даже увидит ее).

Например, эта разметка:

<table>
  <tr v-for="(m,i) in obj">
    <measurement v-bind:name="i" v-bind:data="m"></measurement>
  </tr>
</table>

... становится такой после анализа DOM (перед любым сценарием):

<measurement v-bind:name="i" v-bind:data="m"></measurement> <!-- hoisted outside v-for -->
<table>
  <tr v-for="(m,i) in obj">
  </tr>
</table>

Обратите внимание, что i и m находятся вне контекста v-for l oop, что приводит к Vue ошибкам времени выполнения о i и m не определено (если случайно ваш компонент случайно не объявил их уже). m должен был быть связан с <measurement> data prop, но поскольку это не удалось, data - это просто его начальное значение (также undefined), что приводит к сбою рендеринга {{data.value}} с Error in render: "TypeError: Cannot read property 'value' of undefined".

Чтобы продемонстрировать подъем без этих ошибок времени выполнения и без Vue, запустите приведенный ниже фрагмент кода:

<table style="border: solid green">
  <tr>
    <div>1. hoisted outside</div>
    <td>3. inside table</td>
    2. also hoisted outside
  </tr>
</table>

... затем проверьте результат в DevTools вашего браузера, который должен выглядеть следующим образом:

<div>1. hoisted outside</div>
2. also hoisted outside
<table style="border: solid green">
  <tr>
    <td>3. inside table</td>
  </tr>
</table>

Решение 1: Используйте <tr is="measurement">

Если вы предпочитаете шаблоны DOM, вы можете использовать атрибут is в <tr>, чтобы указать measurement в качестве типа (как предложено в документах Vue и другим ответом ). Для этого сначала необходимо, чтобы шаблон <measurement> использовал <td> или <th> в качестве элемента контейнера внутри <tr>, чтобы быть действительным HTML:

<template id="measurement">
  <tr>
    <td>{{name}} -> {{data.value}}</td>
  </tr>
</template>

<div id="app">
  <table v-for="(m,i) in sortedMeters">
    <tr is="measurement" v-bind:name="i" v-bind:data="m" v-bind:key="i"></tr>
  </table>
</div>

Vue.component('measurement', {
  template: '#measurement',
  props: {
    name: String,
    data: Object
  }
})

new Vue({
  el: '#app',
  data: {
    sortedMeters: {
      apple: {value: 100},
      banana: {value: 200}
    },
  }
})
<script src="https://unpkg.com/vue@2.6.11"></script>
<template id="measurement">
  <tr>
    <td>{{name}} -> {{data.value}}</td>
  </tr>
</template>

<div id="app">
  <table v-for="(m,i) in sortedMeters">
    <tr is="measurement" v-bind:name="i" v-bind:data="m" v-bind:key="i"></tr>
  </table>
</div>

Решение 2: Обтекание <table> в компоненте

Если вы предпочитаете шаблоны DOM, вы можете использовать компонент обертки для <table>, который может содержать <measurement> без предупреждения о подъеме.

Vue.component('my-table', {
  template: `<table><slot/></table>`
})
<div id="app">
  <my-table v-for="(m, i) in sortedMeters">
    <measurement v-bind:name="i" v-bind:data="m"></measurement>
  </my-table>
</div>

Vue.component('measurement', {
  template: '#measurement',
  props: {
    name: String,
    data: Object
  }
})

Vue.component('my-table', {
  template: `<table><slot/></table>`
})

new Vue({
  el: '#app',
  data: {
    sortedMeters: {
      apple: {value: 100},
      banana: {value: 200}
    },
  }
})
<script src="https://unpkg.com/vue@2.6.11"></script>
<template id="measurement">
  <tr>
    <td>{{name}} -> {{data.value}}</td>
  </tr>
</template>

<div id="app">
  <my-table v-for="(m, i) in sortedMeters">
    <measurement v-bind:name="i" v-bind:data="m"></measurement>
  </my-table>
</div>

Решение 3: Переместить <table> разметку в строку шаблона

Вы можете переместить весь <table> в строку шаблона компонента, где можно избежать предостережений шаблона DOM. Точно так же вы можете переместить <table> в отдельный файловый компонент , но я предполагаю, что вам вместо этого нужны шаблоны DOM.

Vue.component('my-table', {
  template: `<div>
    <table v-for="(m, idx) in sortedMeters">
      <measurement v-bind:data="m"></measurement>
    </table>
  </div>`,
  props: {
    sortedMeters: Object
  }
})
<div id="app">
  <my-table v-bind:sorted-meters="sortedMeters"></my-table>
</div>

Vue.component('measurement', {
  template: '#measurement',
  props: {
    name: String,
    data: Object
  }
})

Vue.component('my-table', {
  template: `<div>
    <table v-for="(m,i) in sortedMeters">
      <measurement v-bind:name="i" v-bind:data="m" v-bind:key="i"></measurement>
    </table>
  </div>`,
  props: {
    sortedMeters: Object
  }
})

new Vue({
  el: '#app',
  data: {
    sortedMeters: {
      apple: {value: 100},
      banana: {value: 200}
    },
  }
})
<script src="https://unpkg.com/vue@2.6.11"></script>
<template id="measurement">
  <tr>
    <td>{{name}} -> {{data.value}}</td>
  </tr>
</template>

<div id="app">
  <my-table :sorted-meters="sortedMeters"></my-table>
</div>
1 голос
/ 11 января 2020

Если вы замените

<table v-for="(m, idx) in sortedMeters">
  <measurement v-bind:data="m"></measurement>
</table>

на

<template v-for="(m, idx) in sortedMeters">
  <table>
    <measurement v-bind:data="m"></measurement>
  </table>
</template>

Вы получите рабочий код.

Но вы, скорее всего, захотите использовать

<table>
  <template v-for="(m, idx) in sortedMeters">
    <measurement v-bind:data="m"></measurement>
  </template>
</table>

или

<table>
  <measurement v-for="(m, idx) in sortedMeters" v-bind:data="m"></measurement>
</table>
0 голосов
/ 11 января 2020

Это потому, что вы пропустили <td> внутри <tr>. Без него ваш компонент генерирует недопустимую html разметку и извлекает данные «слотов» за пределами <tr>, вызывая ошибку.

Ваш шаблон должен выглядеть следующим образом:

<template id="measurement">
  <tr>
    <td>{{name}} -> {{data.value}}</td>
  </tr>
</template>

Вам также нужно переместить * От 1009 * до measurement:

<table border="1">
    <tr is="measurement" v-for="(m,index) in obj" :key="index" v-bind:name="index" v-bind:data="m"></measurement>
</table>

Для задания имени компонента можно использовать атрибут is.

Рабочая скрипка: https://jsfiddle.net/cyaj0ukh/

...