使用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><MyInputid="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 来自定义错误消息的渲染。