插槽详解
在 2.6.0 版本之后,slot 以及 slot-scope 由 v-slot 统一替代,但依然未被移除。
1. 什么是插槽
插槽是 vue.js
开发员人根据从 Web Components
规范草案中获取的灵感,设计的一套 API,这套 API 允许我们对组件进行组合。
插槽就是子组件中的提供给父组件使用的一个占位符,用 表示,父组件可以在这个占位符中填充任何模板代码,如 HTML、组件等,填充的内容会替换子组件的<slot></slot>
标签。
类似于 React.js
中的组合(children prop),插槽允许将一些其他的组件作为子组件传递给指定组件。
2. 如何使用
比如下面的代码:
在子组件中:
<template>
<div>
<h1>
插槽的使用
</h1>
<slot></slot>
</div>
</template>
<script>
export default{
name: 'child'
}
</script>
在父组件中:
<template>
<div>
<div>
使用插槽分发内容
</div>
<Child>
<div>
这里的 div 替代了 slot 标签。
</div>
</Child>
</div>
</template>
<script>
import Child from './components/Child';
export default {
name: 'Father',
}
</script>
这样,就会出现:
如果,在子组件中,去除 <slot></slot>
,那么则不会出现 这里的 div 替代了 slot 标签
。
总结:如果子组件没有使用插槽,父组件如果需要往子组件中填充模板或html,是无法做到的。
但是,使用插槽需要注意作用域,子模板里的所有内容都是在子作用域中编译的。父级模板里的所有内容都是在父级作用域中编译的。
比如,还是上面的例子,如果我们这样使用,在父组件中,有一个 props:
<template>
<div>
<div>
使用插槽分发内容
</div>
<child>
<div>
这里的 div 替代了 slot 标签。
</div>
</child>
</div>
</template>
<script>
import child from './components/Child';
export default {
name: 'Father',
props: ['name'],
}
</script>
在 App.vue 中导入 Father 组件:
<template>
<div id="app">
<Father name="father" />
</div>
</template>
<script>
import Father from '@/components/Father';
export default {
name: 'App',
components: {
Father,
}
}
</script>
<style scope>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
height: 100%;
}
</style>
那么,子组件 Child 是访问不到 name,这个 props 的。
3. 后备内容
后备内容,指挥在没有提供内容的时候被渲染。来看一下官网的例子:
在 <submit-button>
组件中:
<button type="submit">
<slot></slot>
</button>
如果在 <slot></slot>
标签中,填入 Submit
内容,那么,当父组件没有向其中添加其他内容的时候,会默认渲染 Submit
:
<html>
<head>
<meta charset="utf-8">
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<body>
<div id="app">
<submit-button>
提交
</submit-button>
</div>
</body>
<script>
Vue.component('submit-button', {
template: '<button type="submit"><slot>Submit</slot></button>'
})
new Vue({
el: '#app'
})
</script>
</html>
结果:
如果在父组件中向插槽中插入了其他的内容:
<html>
<head>
<meta charset="utf-8">
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<body>
<div id="app">
<submit-button>
提交
</submit-button>
<father-button />
</div>
</body>
<script>
Vue.component('submit-button', {
template: '<button type="submit"><slot>Submit</slot></button>'
})
Vue.component('father-button', {
template: '<div><div>添加了插槽内容</div><submit-button><span>订阅</span></submit-button></div>',
});
new Vue({
el: '#app'
})
</script>
</html>
则:
此时,多出了:
4. 具名插槽
描述:具名插槽其实就是给插槽去一个名字。这就意味着,一个子组件可以放多个插槽,而且可以放在不同的地方,而父组件填充内容时,可以根据插槽的名字把内容填充到对应的插槽中。
官网有这样的例子:
<html>
<head>
<meta charset="utf-8">
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<base-layout>
<template v-slot:header>
<h1>Here might be apage title</h1>
</template>
<template v-slot:default>
<p>a paragraph for the main content</p>
<p>And another one.</p>
</template>
<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</base-layout>
</div>
</body>
<script>
Vue.component('base-layout', {
template: '<div class="container">'+
'<header>'+
'<slot name="header"></slot>'+
'</header>' +
'<main>' +
'<slot></slot>' +
'</main>' +
'<footer>' +
'<slot name="footer"></slot>' +
'</footer>' +
'</div>'
})
new Vue({
el: '#app'
})
</script>
</html>
子组件可以根据 <slot></slot>
标签中特殊的属性 attribute
来确定每个插槽唯一的名称,而如果出厂时并没有设置 name
属性,那么,name
属性会默认为 default
。
然后,父组件可以通过 <template>
标签上的 v-slot
属性,根据 name
属性不同的属性值,来选择性地为子组件中的插槽添加内容。
当然,子组件中的每个插槽都可以明确自己的后备内容。
需要注意的是,所有没有
name
属性的<slot></slot>
,都会被默认为拥有default
属性值的<slot></slot>
。
如:
<html>
<head>
<meta charset="utf-8">
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<base-layout>
<template v-slot:header>
<h1>Here might be apage title</h1>
</template>
<template v-slot:default>
<p>a paragraph for the main content</p>
<p>And another one.</p>
</template>
<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</base-layout>
<hr />
<base-layout-again>
<template v-slot:header>
<h1>Here might be apage title</h1>
</template>
<template v-slot:default>
<p>a paragraph for the main content</p>
<p>And another one.</p>
</template>
</base-layout-again>
</div>
</body>
<script>
Vue.component('base-layout', {
template: '<div class="container">'+
'<header>'+
'<slot name="header"></slot>'+
'</header>' +
'<main>' +
'<slot></slot>' +
'</main>' +
'<footer>' +
'<slot name="footer"></slot>' +
'</footer>' +
'</div>'
})
Vue.component('base-layout-again', {
template: '<div class="container">'+
'<header>'+
'<slot name="header"></slot>'+
'</header>' +
'<main>' +
'<slot></slot>' +
'</main>' +
'<footer>' +
'<slot></slot>' +
'</footer>' +
'</div>'
})
new Vue({
el: '#app'
})
</script>
</html>
此时:
当被提供的内容只有默认插槽时,组件的标签才可以被当作插槽的模板来使用。
如:
<html> <head> <meta charset="utf-8"> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <own-default v-slot="slotProps"> {{ slotProps.user.firstname }} </own-default> </div> </body> <script> Vue.component('own-default', { template: '<div class="container">'+ '<slot :user="user"></slot>' + '</div>' , data: () => { return { user: { firstname: 'qian', lastname: 'qian', } } } }); new Vue({ el: '#app' }); </script> </html>
注意!默认插槽的缩写写法不能和具名插槽混用!
5. 作用域插槽
2.6.0 版本以后,slot-scope
属性已经废弃,但依然可以使用
为了让插槽可以访问到子组件中的数据有时候非常有用。例如:
<html>
<head>
<meta charset="utf-8">
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<own-default v-slot:default="slotProps">
{{ slotProps.user.firstname }}
</own-default>
</div>
</body>
<script>
Vue.component('own-default', {
template: '<div class="container">'+
'<slot :user="user"></slot>' +
'</div>' ,
data: () => {
return {
user: {
firstname: 'qian',
lastname: 'qian',
}
}
}
});
new Vue({
el: '#app'
});
</script>
</html>
这里,实际上是提供了插槽的 props,这里的 <slot></slot>
标签绑定了组件中的 user
数据,所以在<slot></slot>
中,我们可以将 user.firstname
作为后备内容。在父级作用域中,我们可以使用带值的 v-slot
来定义我们提供的插槽 prop 的名字。
解构插槽的Prop
我们可以根据 ES2015 里面的规则,解构我们传入的插槽 prop,比如
<current-user v-slot="{ user: person }">
{{ person.firstName }}
</current-user>
将 user 重新命名为 person。
6. 动态插槽名
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
</base-layout>
7. 具名插槽的缩写
跟 v-on
和 v-bind
一样,v-slot
也有缩写,即把参数之前的所有内容 (v-slot:
) 替换为字符 #
。例如 v-slot:header
可以被重写为 #header
:
<base-layout>
<template #header>
<h1>Here might be a page title</h1>
</template>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template #footer>
<p>Here's some contact info</p>
</template>
</base-layout>
然而,和其它指令一样,该缩写只在其有参数的时候才可用。这意味着以下语法是无效的:
<!-- 这样会触发一个警告 -->
<current-user #="{ user }">
{{ user.firstName }}
</current-user>
如果你希望使用缩写的话,你必须始终以明确插槽名取而代之:
<current-user #default="{ user }">
{{ user.firstName }}
</current-user>
8. 其他示例
**插槽 prop 允许我们将插槽转换为可复用的模板,这些模板可以基于输入的 prop 渲染出不同的内容。**在这种设计分装数据逻辑同时允许腹肌组件自定义部分布局的可以复用组件时是最有用的。
官网有这样的例子:
<html>
<head>
<meta charset="utf-8">
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<todo-list v-bind:todos="todos">
<template v-slot:todo="{ todo: todo }">
<span v-if="todo.isComplete">√</span>
{{ todo.text }}
</template>
</todo-list>
</div>
</body>
<script>
Vue.component('todo-list', {
props: ['todos'],
template: '<ul><li v-for="todo in filteredTodos" v-bind:key="todo.id"><slot name="todo" v-bind:todo="todo">{{ todo.text }} </slot> </li></ul>',
data: function() {
return {
filteredTodos: this.todos
}
}
});
new Vue({
el: "#app",
data: {
todos: [{
key: 1,
text: 'Vue',
isComplete: true
},
{
key: 2,
text: 'is',
isComplete: false
},
{
key: 3,
text: 'interesting',
isComplete: true
}
]
}
})
</script>
</html>
最后显示为:
其中
<template v-slot:todo="{ todo: todo }">
中的解构赋值可以写成 v-slot:todo="{ todo }"
这样的缩略写法。hart.js/