TLDR: 在 Vue 传递 DOM 模板之前,浏览器将 <measurement v-bind:name="i" v-bind:data="m"> 提升到 <table> 之外(在 v-for 上下文之外),导致 Vue 中的错误。这是一个已知的caveat of DOM template parsing。
HTML spec requires the <table> contain only specific child elements:
<caption>
<colgroup>
<thead>
<tbody>
<tr>
<tfoot>
-
<script> 或 <template> 与上述混合
同样,content model of <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 循环的上下文之外的,这会导致关于 i 和 m 未定义的 Vue 运行时错误(除非碰巧你的组件已经巧合地声明了它们)。 m 原本打算绑定到 <measurement> 的 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:使用<tr is="measurement">
如果您更喜欢 DOM 模板,您可以使用 <tr> 上的 is 属性将 measurement 指定为类型(如 Vue docs 和另一个 answer 所建议的那样)。这首先要求<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> 移动到 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>