先假设咱们须要实现以下效果,并将其封装成一个组件,固然目的并非要真的封装组件,而是在这个过程当中学习 插槽 的使用以及优化的思路哈: html
<template> <div> <div>{{ title }}</div> <hr> <div> <div v-for="(item, i) in List" :key="i">{{ item }}</div> </div> </div> </template> 复制代码
而后在父组件中这样调用就 ok
了:编程
<template> <custom-list :title="title" :List="books" /> </template> <script> import CustomList from './custom-list.js'; export default { components: { CustomList }, data() { return { title: '书籍列表', books: ['Dom编程艺术', '你不知道的Javascript', 'CSS世界', 'HTML入门教程'], }; }, }; </script> 复制代码
Emmm。。乍一看彷佛没有任何毛病,甚至数据变化了也是能够知足要求的。markdown
然而,这样就够了吗?需求老是善变的,好比说,有一天又要知足以下效果呢?学习
这个时候就到咱们的 插槽 登场了。插槽容许咱们将子组件中的内容分发到父组件,由父组件决定子组件要渲染的内容。优化
仍是以上述组件为例,咱们须要将子组件中 title
的内容分发出去,由父组件将内容传进来。此时咱们能够这样修改子组件:spa
<template> <div> <div> <slot></slot> </div> <hr> <div> <div v-for="(item, i) in List" :key="i">{{ item }}</div> </div> </div> </template> 复制代码
而后在父组件中调用:code
<template> <custom-list :List="books"> <span><icon type="waves"> 书籍列表</span> </custom-list> </template> <script> import CustomList from './custom-list.js'; export default { components: { CustomList }, data() { return { title: '书籍列表', books: ['Dom编程艺术', '你不知道的Javascript', 'CSS世界', 'HTML入门教程'], }; }, }; </script> 复制代码
这样的话,父组件中包裹的内容就会替代 <slot>
标签,因此最终的渲染结果就是这样的:component
<template> <div> <div> <span><icon type="waves"> 书籍列表</span> </div> <hr> <div> <div v-for="(item, i) in List" :key="i">{{ item }}</div> </div> </div> </template> 复制代码
顺便提一嘴:在 <slot>
标签中也是能够放置内容的,如:<slot> 标题 </slot>
, 这表示插槽的默认内容,只有当父组件没有经过插槽传递内容的时候,才会显示该默认内容。orm
同理咱们的 content
的内容也是须要分发出去的,因而咱们再修改:htm
<template> <div> <div> <slot></slot> </div> <hr> <slot></slot> </div> </template> 复制代码
这样一来又有问题了:咱们定义了两个 怎么去区分它们的内容呢,总不能靠书写顺序吧?这显然是不靠谱的。因而,就有了 具名插槽。
顾名思义就是带名字的插槽,便于多个插槽之间的区分。而后咱们继续修改:
<template> <div> <div> <slot name="title"></slot> </div> <hr> <slot name="content"></slot> </div> </template> 复制代码
给每一个插槽定义一个 name
属性,而后咱们在父组件传值的时候带上对应插槽的 name
就好了。
<template> <custom-list> <template v-slot:title> <span><icon type="waves"> 书籍列表</span> <template> <template v-slot:content> <div> <div v-for="(item, i) in book" :key="i"> <icon :type="item.icon">{{ item.name }} </div> </div> </template> </custom-list> </template> <script> import CustomList from './custom-list.js'; export default { components: { CustomList }, data() { return { title: '书籍列表', books: [ { name: 'Dom编程艺术',icon: 'face' }, { name: '你不知道的Javascript', icon: 'favorite' }, { name: 'CSS世界', icon: 'av_timer'}, { name: 'HTML入门教程', icon: 'star_half' }, ], }; }, }; </script> 复制代码
在向具名插槽提供内容的时候,咱们能够在一个
<template>
元素上使用v-slot
指令,并以v-slot
的参数的形式提供其名称。
这下可好了,连数据都不用传了,List
在子组件根本用不到,数据直接在父组件中就渲染了,并且每次使用这个组件的时候都要写一遍 v-for
,那为何不把 v-for
写在子组件中呢?这样就只须要写一次了。 那咱们再来修改子组件:
<template> <div> <div> <slot name="title"></slot> </div> <hr> <div> <div v-for="(item, i) in List"> <slot name="item"></slot> </div> </div> </div> </template> 复制代码
这样又有问题了:我要显示 item
可是 item
是子组件中的数据,当父组件经过 v-slot
传递内容进来的时候,<slot>{{ item }}</slot>
的内容就会被传进来的内容替换了。这咋搞呢?思考一下,若是咱们将数据给父组件,在父组件中显示不就解决了么?那么这个时候就要轮到 做用域插槽
登场了。
做用域插槽容许咱们将子组件的数据传给父组件,这不正好解决了上面的问题么?下面咱们再调整子组件
<template> <div> <div> <slot name="title"></slot> </div> <hr> <div> <div v-for="(item, i) in List"> <slot name="item" :row="item"></slot> </div> </div> </div> </template> 复制代码
此时咱们将每一个 item
的值经过 row
属性传给了 <slot>
, 而后咱们在父组件中取出这个值:
<template> <custom-list> <template v-slot:title> <span><icon type="waves"> 书籍列表</span> <template> <!--实际传的数据格式为: { row:{ name: xx, icon: xx } },因此这里可使用结构赋值--> <template v-slot:item="{ row }"> <icon :type="row.icon">{{ row.name }} </template> </custom-list> </template> 复制代码
这样咱们的组件就完成了,不只灵活性更高,代码健壮性也更好。 总得来讲,插槽仍是很好用的,特别是在封装组件的时候。