【问题标题】:Why can't vue find variable in loop?为什么vue在循环中找不到变量?
【发布时间】:2020-01-13 19:32:41
【问题描述】:

我正在努力开发一个简单的组件并在循环中使用它:

<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,它就会显示不同的错误。

更新 循环的目的是生成多个表。每个表的行将由多个measurements创建

【问题讨论】:

  • 虽然我不知道您的问题的答案(我也对此感到困惑),但检查生成的 HTML 表明您正在创建两个表(每个用于迭代创建一个 table 元素) 如果可行,每个都包含一个tr。您不打算循环tr 元素吗?但即使循环 tr 元素也会导致错误...
  • 这实际上是预期的 - 最后我将需要多个表用于用例。
  • 那么我回答的前半部分应该回答了你的问题。好吧,不回答它,但给你一个解决方案,因为我不知道为什么你的不起作用。

标签: vue.js vuejs2 vue-component


【解决方案1】:

TLDR: 在 Vue 传递 DOM 模板之前,浏览器将 &lt;measurement v-bind:name="i" v-bind:data="m"&gt; 提升到 &lt;table&gt; 之外(在 v-for 上下文之外),导致 Vue 中的错误。这是一个已知的caveat of DOM template parsing


HTML spec requires the &lt;table&gt; contain only specific child elements:

  • &lt;caption&gt;
  • &lt;colgroup&gt;
  • &lt;thead&gt;
  • &lt;tbody&gt;
  • &lt;tr&gt;
  • &lt;tfoot&gt;
  • &lt;script&gt;&lt;template&gt; 与上述混合

同样,content model of &lt;tr&gt; 是:

  • &lt;td&gt;
  • &lt;th&gt;
  • &lt;script&gt;&lt;template&gt; 与上述混合

兼容浏览器的 DOM 解析器会自动将不允许的元素(例如 &lt;measurement&gt;)提升到表格之外。这发生在 脚本阶段之前(甚至在 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>

注意 im 是如何在 v-for 循环的上下文之外的,这会导致关于 im 未定义的 Vue 运行时错误(除非碰巧你的组件已经巧合地声明了它们)。 m 原本打算绑定到 &lt;measurement&gt;data 属性,但由于失败,data 只是它的初始值(也是 undefined),导致 {{data.value}} 的渲染失败并带有 @ 987654359@.

要演示没有这些运行时错误且没有 Vue 的提升,请运行以下代码 sn-p:

<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:使用&lt;tr is="measurement"&gt;

如果您更喜欢 DOM 模板,您可以使用 &lt;tr&gt; 上的 is 属性将 measurement 指定为类型(如 Vue docs 和另一个 answer 所建议的那样)。这首先要求&lt;measurement&gt; 模板使用&lt;td&gt;&lt;th&gt; 作为&lt;tr&gt; 中的容器元素才能成为有效的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:将 &lt;table&gt; 包裹在组件中

如果您更喜欢 DOM 模板,您可以为 &lt;table&gt; 使用包装器组件,它可以包含 &lt;measurement&gt; 而没有提升警告。

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:将 &lt;table&gt; 标记移动到模板字符串中

您可以将整个 &lt;table&gt; 移动到组件的模板字符串中,这样可以避免 DOM 模板警告。 同样,您可以将 &lt;table&gt; 移动到 single file component,但我认为您对 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>

【讨论】:

  • 哇。很好的答案!
【解决方案2】:

如果你替换

<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>

【讨论】:

  • 我可以确认用模板包裹表格是可行的。但是-只要我不添加子组件而是手动执行 tr 的 html,我就可以很好地使用表上的循环。失败的原因仍然是个谜。
  • 将表格包装在&lt;template&gt; 中仅因为其内容是惰性的(DOM 解析器未解析,直到被 JavaScript 调用)才有效。
【解决方案3】:

这是因为您在&lt;tr&gt; 中缺少&lt;td&gt;。如果没有它,您的组件会生成无效的 html 标记并在 &lt;tr&gt; 之外提取“插槽”数据,从而导致错误。

您的模板应如下所示:

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

您还需要将v-for 移动到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/

【讨论】:

  • 您确定这是无效的 HTML 内容吗?我已经将他的小提琴更改为具有正确的 HTML 代码,但即使这样也失败了。这是一个示例:jsfiddle.net/hpc9Lmjr
  • 达到了同样的结论。我需要多个表,但即使使用正确的 html 也会失败。
  • @andig 对于问题的 HTML 无效 (+1),这个答案基本上是正确的,尽管 IMO 的解释有些不完整。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-03-14
  • 1970-01-01
  • 2014-10-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-05
相关资源
最近更新 更多