使用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 來自定義錯誤消息的渲染。