通过 <slot>
标签来定义插槽, 可以通过 name
属性来定义具名插槽, 默认插槽是 default
<template>
<button class="app-button" v-on="$attrs">
<slot></slot>
</button>
</template>
<style scoped>
.app-button {
margin: 10px;
padding: 10px 15px;
border: none;
border-radius: 5px;
background-color: rgb(39, 224, 163);
color: white;
font-size: 20px;
}
</style>
<template>
<AppButton>press me</AppButton>
</template>
<script>
import AppButton from './components/AppButton.vue'
export default {
name: 'App',
components: {
AppButton
}
}
</script>
子控件通过 v-on="$attrs"
来传递点击事件,父控价通过 @click="xxx"
来响应点击事件。
<template>
<button class="app-button" v-on="$attrs">
<slot></slot>
</button>
</template>
<style scoped>
.app-button {
margin: 10px;
padding: 10px 15px;
border: none;
border-radius: 5px;
background-color: rgb(39, 224, 163);
color: white;
font-size: 20px;
}
</style>
<template>
<AppButton @click="log">press me</AppButton>
</template>
<script>
import AppButton from './components/AppButton.vue'
export default {
name: 'App',
components: {
AppButton
},
methods: {
log() {
console.log('clicked');
}
}
}
</script>
通过提供一个带函数类型的 props, 把数据传递给父控件
{{ secondrow(item) }}
props: {
secondrow: {
type: Function,
default: () => {}
}
}
<AppUserList :secondrow="user => user.email"></AppUserList>
<template>
<section>
<slot name="title">Users</slot>
<ul class="userlist" v-if="state === 'loaded'">
<li v-for="item in data.results" :key="item.email">
<div class="list-item">
<img width="48" height="48" :src="item.picture.large" :alt="item.name.first + ' ' + item.name.last" />
<div class="text">
<div> {{ item.name.first }}</div>
<slot></slot>
{{ secondrow(item) }}
</div>
</div>
</li>
</ul>
<slot v-if="state === 'loading'" name="loading">
loading...
</slot>
<slot v-if="state === 'failed'" name="error">
Oops, something went wrong.
</slot>
</section>
</template>
<script>
const states = {
idle: "idle",
loading: "loading",
loaded: "loaded",
failed: "failed"
}
export default {
props: {
secondrow: {
type: Function,
default: () => {}
}
},
data() {
return {
state: 'idle',
error: undefined,
data: undefined,
states
}
},
mounted() {
this.load();
},
methods: {
async load() {
this.state = "loading";
this.error = undefined;
this.data = undefined;
try {
setTimeout(async() => {
const response = await fetch("https://randomuser.me/api/?results=5");
const json = await response.json();
console.log("data = ", json);
this.state = "loaded";
this.data = json;
return response;
}, 1000);
} catch(error) {
this.state = "failed"
this.error = error;
console.log("--- error: ", error);
return error;
}
}
}
}
</script>
<style scoped>
.userlist li {
display: inline;
}
.list-item {
display: inline;
display: flex;
justify-content: flex-start;
margin: 10px 0;
}
.text {
font-weight: bold;
font-size: 19px;
display: block;
margin-left: 20px;
text-align: left;
}
</style>
<template>
<AppUserList :secondrow="user => user.email"></AppUserList>
</template>
<script>
import AppUserList from '@/components/AppUserList'
export default {
name: 'App',
components: {
AppUserList
}
}
</script>
使用 Scoped Slot
将数据传递给父控件
<slot name="secondrow" :item="item"></slot>
<AppUserList>
<template #secondrow="slotProps">
<a :href="'tel:' + slotProps.item.phone">
{{ slotProps.item.phone}}
</a>
<a :href="'mailto:' + slotProps.item.email">
{{ slotProps.item.email}}
</a>
</template>
</AppUserList>
<template>
<section>
<slot name="title">Users</slot>
<ul class="userlist" v-if="state === 'loaded'">
<li v-for="item in data.results" :key="item.email">
<div class="list-item">
<img width="48" height="48" :src="item.picture.large" :alt="item.name.first + ' ' + item.name.last" />
<div class="text">
<div> {{ item.name.first }}</div>
<slot name="secondrow" :item="item"></slot>
</div>
</div>
</li>
</ul>
<slot v-if="state === 'loading'" name="loading">
loading...
</slot>
<slot v-if="state === 'failed'" name="error">
Oops, something went wrong.
</slot>
</section>
</template>
<script>
const states = {
idle: "idle",
loading: "loading",
loaded: "loaded",
failed: "failed"
}
export default {
data() {
return {
state: 'idle',
error: undefined,
data: undefined,
states
}
},
mounted() {
this.load();
},
methods: {
async load() {
this.state = "loading";
this.error = undefined;
this.data = undefined;
try {
setTimeout(async() => {
const response = await fetch("https://randomuser.me/api/?results=5");
const json = await response.json();
console.log("data = ", json);
this.state = "loaded";
this.data = json;
return response;
}, 1000);
} catch(error) {
this.state = "failed"
this.error = error;
console.log("--- error: ", error);
return error;
}
}
}
}
</script>
<style scoped>
.userlist li {
height: auto;
display: inline;
}
.list-item {
display: inline;
display: flex;
justify-content: flex-start;
margin: 10px 0;
}
.text {
font-weight: bold;
font-size: 19px;
display: block;
margin-left: 20px;
text-align: left;
}
</style>
<template>
<AppUserList>
<template #secondrow="slotProps">
<a :href="'tel:' + slotProps.item.phone">
{{ slotProps.item.phone}}
</a>
<a :href="'mailto:' + slotProps.item.email">
{{ slotProps.item.email}}
</a>
</template>
</AppUserList>
</template>
<script>
import AppUserList from '@/components/AppUserList'
export default {
name: 'App',
components: {
AppUserList
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
通过给Slot绑定一个函数, 使父控件可以传递事件到Slot
<template>
<section>
<slot name="title">Users</slot>
<ul class="userlist" v-if="state === 'loaded'">
<li v-for="item in data.results" :key="item.email">
<div class="list-item">
<img width="48" height="48" :src="item.picture.large" :alt="item.name.first + ' ' + item.name.last" />
<div class="text">
<div> {{ item.name.first }}</div>
<slot name="secondrow" :item="item" :remove="remove"></slot>
</div>
</div>
</li>
</ul>
<slot v-if="state === 'loading'" name="loading">
loading...
</slot>
<slot v-if="state === 'failed'" name="error">
Oops, something went wrong.
</slot>
</section>
</template>
<script>
const states = {
idle: "idle",
loading: "loading",
loaded: "loaded",
failed: "failed"
}
export default {
data() {
return {
state: 'idle',
error: undefined,
data: undefined,
states
}
},
mounted() {
this.load();
},
methods: {
async load() {
this.state = "loading";
this.error = undefined;
this.data = undefined;
try {
setTimeout(async() => {
const response = await fetch("https://randomuser.me/api/?results=5");
const json = await response.json();
console.log("data = ", json);
this.state = "loaded";
this.data = json;
return response;
}, 1000);
} catch(error) {
this.state = "failed"
this.error = error;
console.log("--- error: ", error);
return error;
}
},
remove(item) {
this.data.results = this.data.results.filter((entry) => {
return entry.email !== item.email;
});
}
}
}
</script>
<style scoped>
.userlist li {
height: auto;
display: inline;
}
.list-item {
display: inline;
display: flex;
justify-content: flex-start;
margin: 10px 0;
}
.text {
font-weight: bold;
font-size: 19px;
display: block;
margin-left: 20px;
text-align: left;
}
</style>
<template>
<AppUserList>
<template #secondrow="{remove, item: user}">
<AppButton @click="remove(user)">{{ user.name.first }}</AppButton>
</template>
</AppUserList>
</template>
<script>
import AppButton from './components/AppButton.vue'
import AppUserList from '@/components/AppUserList'
export default {
name: 'App',
components: {
AppButton,
AppUserList
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>