使用slot进行灵活的组件渲染
作用域slot是 Vue.js 中的一种强大机制,它允许父组件自定义子组件内容的呈现。与仅向下传递数据的常规 props 不同,作用域 slot 为父级提供了一个模板,然后子级可以填充数据。这提供了高度的灵活性和可重用性,使您能够创建可适应各种上下文的组件,而无需修改其核心逻辑。本章将探索 scoped slot 的概念、它们的语法,以及如何使用它们来构建灵活且可重用的组件。
了解作用域插槽
作用域插槽 是一种特殊类型的插槽,它允许父组件访问子组件的数据。这是通过将数据作为 props 传递给插槽来实现的。然后,父组件使用此数据以自定义方式呈现插槽内容。
基本语法
使用作用域插槽的基本语法包括在子组件中定义一个插槽,然后在父组件中为该插槽提供模板。
子组件(例如 MyList.vue
):
<template>
<div>
<ul>
<li v-for="item in items" :key="item.id">
<slot name="item" :item="item">{{ item.name }}</slot>
</li>
</ul>
</div>
</template>
<script>
export default {
props: {
items: {
type: Array,
required: true,
},
},
};
</script>
在这个例子中,<slot>
元素有一个 name
属性设置为 “item” 和一个 :item
属性,该属性将当前item
从 v-for
循环绑定到插槽的范围。{{ item.name }}
是父组件未为槽提供自定义模板时将呈现的回退内容。
父组件:
<template>
<div>
<MyList :items="myItems">
<template v-slot:item="slotProps">
<strong>{{ slotProps.item.name }}</strong> - <em>{{ slotProps.item.description }}</em>
</template>
</MyList>
</div>
</template>
<script>
import MyList from './MyList.vue';
export default {
components: {
MyList,
},
data() {
return {
myItems: [
{ id: 1, name: 'Apple', description: 'A crisp and juicy fruit' },
{ id: 2, name: 'Banana', description: 'A sweet and potassium-rich fruit' },
],
};
},
};
</script>
在这里, <template v-slot:item="slotProps">
语法为 MyList
组件中的 “item” 插槽定义了一个作用域插槽。slotProps
变量是一个对象,其中包含从子组件(在本例中为 item
对象)传递的数据。然后,父组件使用此数据以自定义格式呈现插槽内容。
速记语法
Vue.js 使用 #
字符为作用域插槽提供简写语法。可以使用简写语法重写前面的示例,如下所示:
<template>
<div>
<MyList :items="myItems">
<template #item="slotProps">
<strong>{{ slotProps.item.name }}</strong> - <em>{{ slotProps.item.description }}</em>
</template>
</MyList>
</div>
</template>
#item=“slotProps”
等同于 v-slot:item=“slotProps”。
这种速记语法更简洁,通常是首选。
带有 Scoped Props 的默认插槽
作用域插槽也可以与默认插槽(没有 name
属性的插槽)一起使用。在这种情况下,父组件为默认插槽提供模板,并且可以访问从子组件传递的数据。
子组件:
<template>
<div>
<slot :message="greeting"></slot>
</div>
</template>
<script>
export default {
data() {
return {
greeting: 'Hello from the child!',
};
},
};
</script>
父组件:
<template>
<div>
<MyComponent>
<template #default="slotProps">
{{ slotProps.message }}
</template>
</MyComponent>
</div>
</template>
<script>
import MyComponent from './MyComponent.vue';
export default {
components: {
MyComponent,
},
};
</script>
在此示例中,子组件将 message
属性传递给 default 插槽。然后,父组件使用此 prop 来渲染插槽内容。
实际示例和演示
让我们探索一些实际的例子,说明如何使用作用域 slot 来构建灵活且可重用的组件。
示例 1:可自定义的表组件
作用域 slots 的一个常见用例是创建可自定义的 table 组件。table 组件可以提供表的基本结构,而父组件可以定义每个单元格的呈现方式。
表格组件 (MyTable.vue
):
<template>
<table>
<thead>
<tr>
<th v-for="header in headers" :key="header.key">{{ header.label }}</th>
</tr>
</thead>
<tbody>
<tr v-for="row in items" :key="row.id">
<td v-for="header in headers" :key="header.key">
<slot :name="header.key" :row="row">{{ row[header.key] }}</slot>
</td>
</tr>
</tbody>
</table>
</template>
<script>
export default {
props: {
headers: {
type: Array,
required: true,
},
items: {
type: Array,
required: true,
},
},
};
</script>
父组件:
<template>
<div>
<MyTable :headers="tableHeaders" :items="tableData">
<template #name="slotProps">
<strong>{{ slotProps.row.name }}</strong>
</template>
<template #age="slotProps">
<em>{{ slotProps.row.age }}</em>
</template>
</MyTable>
</div>
</template>
<script>
import MyTable from './MyTable.vue';
export default {
components: {
MyTable,
},
data() {
return {
tableHeaders: [
{ key: 'name', label: 'Name' },
{ key: 'age', label: 'Age' },
{ key: 'city', label: 'City' },
],
tableData: [
{ id: 1, name: 'John Doe', age: 30, city: 'New York' },
{ id: 2, name: 'Jane Smith', age: 25, city: 'Los Angeles' },
],
};
},
};
</script>
在此示例中,MyTable
组件基于 headers
和 items
属性呈现一个表。父组件使用作用域插槽来自定义 “name” 和 “age” 列的呈现。“city” 列将使用 MyTable
组件中定义的默认内容。
示例 2:可自定义的列表组件
另一个常见用例是创建可自定义的列表组件。列表组件可以提供列表的基本结构,而父组件可以定义每个项目的呈现方式。
列表组件 (MyList.vue
):
<template>
<ul>
<li v-for="item in items" :key="item.id">
<slot name="item" :item="item">{{ item.name }}</slot>
</li>
</ul>
</template>
<script>
export default {
props: {
items: {
type: Array,
required: true,
},
},
};
</script>
父组件:
<template>
<div>
<MyList :items="myItems">
<template #item="slotProps">
<a :href="slotProps.item.url">{{ slotProps.item.name }}</a>
</template>
</MyList>
</div>
</template>
<script>
import MyList from './MyList.vue';
export default {
components: {
MyList,
},
data() {
return {
myItems: [
{ id: 1, name: 'Google', url: 'https://www.google.com' },
{ id: 2, name: 'Facebook', url: 'https://www.facebook.com' },
],
};
},
};
</script>
在此示例中,MyList
组件根据 items
属性呈现项目列表。父组件使用一个有范围的插槽来自定义每个项目的渲染,将其转换为链接。
示例 3:具有验证的表单输入组件
范围插槽可用于创建高度可定制的表单输入组件。该组件可以处理输入逻辑和验证,而父组件可以自定义输入的外观和错误消息。
输入组件 (MyInput.vue
):
<template>
<div>
<label :for="id">{{ label }}</label>
<input
:id="id"
:type="type"
:value="value"
@input="$emit('update:value', $event.target.value)"
/>
<div v-if="error">
<slot name="error" :error="error">{{ error }}</slot>
</div>
</div>
</template>
<script>
import { ref, watch } from 'vue';
export default {
props: {
id: {
type: String,
required: true,
},
label: {
type: String,
required: true,
},
type: {
type: String,
default: 'text',
},
value: {
type: String,
default: '',
},
rules: {
type: Array,
default: () => [],
},
},
emits: ['update:value'],
setup(props) {
const error = ref('');
watch(
() => props.value,
(newValue) => {
for (const rule of props.rules) {
const validationResult = rule(newValue);
if (validationResult) {
error.value = validationResult;
return;
}
}
error.value = '';
}
);
return {
error,
};
},
};
</script>
父组件:
<template>
<div>
<MyInput
id="name"
label="Name"
type="text"
:value="name"
@update:value="name = $event"
:rules="[requiredRule, minLengthRule]"
>
<template #error="slotProps">
<span style="color: red;">{{ slotProps.error }}</span>
</template>
</MyInput>
</div>
</template>
<script>
import MyInput from './MyInput.vue';
export default {
components: {
MyInput,
},
data() {
return {
name: '',
};
},
setup() {
const requiredRule = (value) => {
if (!value) {
return 'This field is required.';
}
return null;
};
const minLengthRule = (value) => {
if (value.length < 3) {
return 'This field must be at least 3 characters long.';
}
return null;
};
return {
requiredRule,
minLengthRule,
};
},
};
</script>
在此示例中,MyInput
组件处理输入逻辑和验证。父组件使用 scoped slot 来自定义错误消息的渲染。