Vuejs 3如何将变量传递给槽内的所有元素

  • 本文关键字:元素 变量 Vuejs vue.js vuejs3
  • 更新时间 :
  • 英文 :


我使用VueJs 3创建一个动态表组件,它涉及到向槽内的所有子组件发送向下循环索引变量

组件的用法如下

<template>
<h1>my table</h1>
<MyTable
:source="[
{ fname: 'Jhon', lname: 'Smith' },
{ fname: 'Arthur', lname: 'Cromer' },
]"
>
<MyTableColumn cellTitle="First Name" cellValue="fname"></MyTableColumn>
<MyTableColumn cellTitle="Last Name" cellValue="lname"></MyTableColumn>
</MyTable>
</template>

上面显示了我想要实现的,您可以设置表数据源(数组)和任意数量的列,其中每个列接受标题和单元格值。

我不确定如何将迭代索引(v-for index)发送到槽内的所有组件。

这是MyTable。vue模板

<template>
<table>
<tr v-for="(row, index) in $props.source" :key="index">
{{
(currentRow = index)
}}
<slot></slot>
</tr>
</table>
</template>

我试过作用域插槽,但没有帮助。并尝试了提供/注入技术,从MyTable中提供索引。vue和注射MyTableColumn的每个实例。Vue,但这并没有像预期的那样工作,因为注入的变量总是具有最后一个索引。不确定是否需要使用渲染函数

我花了几天的时间试图找到一个解决方案,但没有运气。

在这里你可以访问完整的代码演示,它有意想不到的行为

显示行

你可以使用作用域槽将数据从MyTable.vue传递到它的子槽:

  1. MyTable.vue中,将indexrow的值绑定到<slot>:
<tr v-for="(row, index) in $props.source" :key="index">
<slot :index="index" :row="row"></slot>
</tr>
  1. App.vue中,通过<template>上的v-slot访问MyTable.vue的插槽道具,并将其绑定到MyTableColumn的道具上:
<MyTable
:source="[
{ fname: 'Jhon', lname: 'Smith' },
{ fname: 'Arthur', lname: 'Cromer' },
]"
>
👇
<template v-slot="{ index, row }">
👇              👇
<MyTableColumn cellTitle="First Name" :index="index" :cellValue="row.fname"></MyTableColumn>
👇              👇
<MyTableColumn cellTitle="Last Name" :index="index" :cellValue="row.lname"></MyTableColumn>
</template>
</MyTable>
<<h3>渲染头/h3>
  1. MyTable.vue中,添加headersprop以包含列标题数组,并将它们呈现在表体上方:
defineProps({
headers: Array,
})
<table>
<tr>
<td v-for="header in headers">{{ header }}</td>
</tr>
<!-- table body... -->
</table>
  1. App.vue中,将所需的列标题绑定到<MyTable>.headers:
<MyTable :headers="['First Name', 'Last Name']" ⋯>

演示

使用作用域槽和相关样板代码的替代方法是在渲染函数中为子函数添加道具,这是通过修改vnode对象来完成的,类似于这个答案。

MyTable可以有一组有限的组件,这些组件可能会被用作它的子组件,并且可能会得到特殊的处理,例如MyTableColumn

() => {
const tableChildren = slots.default?.() || [];
const tableColumns = tableChildren.filter(vnode => vnode.type === MyTableColumn);
const tableHeaders = tableColumns.map(vnode => vnode.props.cellTitle);
return h('table', {}, [
h('tr', {}, [
...tableHeaders.map(header => h('td', {}, header)),
]),
...props.source.map((row, index) => {
return h('tr', {}, [
...tableColumns.map(vnode => {
const name = vnode.props.cellValue
return {
...vnode,
props: {
...vnode.props,
index,
value: row[name]
}
};
})
])
})
...

这里额外的indexvalue道具在渲染之前被添加到MyTableColumnvnodes。

这可以通过使MyTableColumn根本不被呈现来改变,只是向MyTable提供有关列的必要信息,并为它们提供可选的自定义布局。这种方法在React生态系统中更流行,但也可以在Primevue DataTable中看到,例如。可以看到,Column组件没有模板,因此没有渲染。

让我们从一个简单的插槽示例开始:

<template>
content which always renders
<slot name="slotName" :foo="'bar'">
content to be rendered if slot is not present
</slot>
some more content which always renders
</template>

给定上面的槽式组件,下面的标记:

<Slotted>
<template #slotName="{foo}">
replacement content for slot - {{ foo }}
</template>
</Slotted>

会产生

content which always renders
replacement content for slot - bar
some more content which always renders

,而<Slotted />会产生:

content which always renders
content to be rendered if slot is not present
some more content which always renders

实现上面的表,我们定义呈现列字段和为每个字段定义一个槽的细胞,另一个用于每个字段的标题。他们是动态的。

若要替换任何列单元格或列标题,请为该列的单元格提供模板。在默认内容中,嵌套一个通用槽用于所有单元格(#cell),一个用于所有标题(#header)。如果提供,将用于任何没有指定模板的单元格(或页眉)。如果没有提供,则为默认内容。

在代码:

Table.vue

<template>
<table>
<thead>
<tr>
<th v-for="{label, key} in fields" :key="key">
<slot :name="`header(${key})`"
v-bind="{ label, propName: key }">
<slot name="header">
{{ label }}
</slot>
</slot>
</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in rows" :key="index">
<td v-for="field in fields" :key="field.key">
<slot :name="`cell(${field.key})`"
v-bind="{ 
item, 
index, 
value: item[field.key],
field
}">
<slot name="cell"
v-bind="{ 
item, 
index, 
value: item[field.key],
field
}">
{{ item[field.key] }}
</slot>
</slot>
</td>
</tr>
</tbody>
</table>
</template>

用作:

<Table :rows="rows" :fields="fields">
<template #cell(fname)="{ value, index }">
<span style="color: red" v-text="`${index} - ${value}" />
</template>
<template #header(fname)="{ label }">
<span style="color: blue" v-text="label" />
</template>
<template #header="{ label }">
<!-- applies to all header cells which don't have 
a #header({key}) template provided.
The ones which do (eg: `#header(fname)`) override this. -->
<span :style="{fontStyle: 'italic'}" v-text="label" />
</template>
</Table>

工作的例子。

这种方法的美妙之处在于,现在您可以使用这个<Table />来呈现任何项,而不必更改<Table />组件。你只需要提供应该呈现其他内容而不是默认内容的插槽。

注意:在每个槽内,您可以访问绑定到槽作用域的任何内容。在本例中,{ item, index, value, field }用于单元格,{ label, key }用于标题单元格。显然,你并不局限于这些。你可以绑定任何你想要的。

最新更新