手机网站页面如何制作做百度关键词排名的公司
为学习Vue基础知识,我动手操作通关了Vue演练场,该演练场教程的目标是快速体验使用 Vue 是什么感受,设置偏好时我选的是选项式 + 单文件组件。以下是我结合深入指南写的总结笔记,希望对Vue初学者有所帮助。
文章目录
- 十五. 插槽
- 插槽内容与出口
- 渲染作用域
- 默认内容
- 具名插槽
- 条件插槽
- 动态插槽名
- 作用域插槽
- 具名作用域插槽
- 高级列表组件示例
- 无渲染组件
十五. 插槽
插槽内容与出口
父组件可以用props
向子组件传递js表达式,emits
向子组件传递事件,那能不能直接向子组件传递定义好的模板内容呢?
这就是插槽的作用。
<!-- Dialog.vue -->
<div class="dialog"><slot />
</div>
<Dialog><div>对话框内容</div>
</Dialog>
最终渲染出的 DOM 是这样:
<div class="dialog"><div>对话框内容</div>
</div>
渲染作用域
由于插槽内容是在父组件里定义的,所以它能访问到父组件的数据作用域,不能访问子组件的。
<!-- Dialog.vue -->
<script>
export default {data() {return {msg: '来自子组件的内容'};}
}
</script>
<template><div class="dialog"><slot /></div>
</template>
<script>
import Dialog from './Dialog.vue';
export default {components: {Dialog},data() {return {msg: '来自父组件的内容'};}
}
</script>
<template><Dialog><div>{{msg}}</div></Dialog>
</template>
最终渲染出的 DOM 是这样:
<div class="dialog"><div>来自父组件的内容</div>
</div>
默认内容
<slot>
标签之间的内容可以作为默认内容,如果父组件使用了含有插槽的子组件但没有传入插槽内容,子组件中的插槽就使用默认内容。
<!-- Dialog.vue -->
<div class="dialog"><slot>默认内容</slot>
</div>
<Dialog><div>对话框内容</div>
</Dialog>
<Dialog />
最终渲染出的 DOM 是这样:
<div class="dialog"><div>对话框内容</div>
</div>
<div class="dialog">默认内容
</div>
具名插槽
有时候我们希望子组件能接收多个插槽内容,比如希望一个对话框组件支持分别接收头部内容、主体内容、底部内容。对于这种场景, 元素可以有一个特殊的 attribute name
,用来给各个插槽分配唯一的 ID。
这类带 name 的插槽被称为具名插槽 (named slots)。没有提供 name 的 <slot>
出口会被隐式地命名为“default”。
<!-- Dialog.vue -->
<div class="dialog"><!-- 对话框头部 --><header class="card-header"><slot name="header" /></header><!-- 对话框主体 --><main class="card-main"><slot></slot></main><!-- 对话框尾部 --><footer class="card-footer"><slot name="footer">默认底部</slot></footer>
</div>
与之<slot name="header">
匹配的是包含模板内容的<template v-slot:header>
,或简写为<template #header>
:
<Dialog><!-- 对话框头部 --><template #header><div>我的标题</div></template><!-- 对话框主体 --><template #default><p>我的内容1</p><p>我的内容2</p></template><!-- 对话框尾部 --><template #footer><button>取消</button><button>确定</button></template>
</Dialog>
当一个组件同时接收默认插槽和具名插槽时,所有位于顶级的非 <template>
节点都被隐式地视为默认插槽的内容。所以也可以省略掉<template #default>
,把其中内容直接作子组件的直接子元素,但不能混用,即子组件的直接子元素中不能同时出现<template #default>
和非 <template>
节点。
<Dialog><!-- 对话框头部 --><template #header><div>我的标题</div></template><!-- 对话框主体 --><p>我的内容1</p><p>我的内容2</p><!-- 对话框尾部 --><template #footer><button>取消</button><button>确定</button></template>
</Dialog>
最终渲染出的 HTML 如下:
<div class="dialog"><!-- 对话框头部 --><header class="card-header"><div>我的标题</div></header><!-- 对话框主体 --><main class="card-main"><p>我的内容1</p><p>我的内容2</p></main><!-- 对话框尾部 --><footer class="card-footer"><button>取消</button><button>确定</button></footer>
</div>
推荐在使用具名插槽的时候,为默认插槽使用显式的 <template>
标签,不容易混淆,更加可读。
条件插槽
在上面的例子中,我们为对话框组件的 header、footer 或 default 等插槽设置了margin等样式:
<header class="card-header"><slot name="header" />
</header>
但如果用户希望创建一个没有头部的对话框,于是不传header插槽内容(且没有默认插槽内容),那就会出现对话框顶部渲染出一个空的<header>
。
<header class="card-header"></header>
那么有没有办法在根据header插槽存在与否来决定要不要渲染<header>
标签呢?
可以结合 $slots 属性与 v-if 来实现:
<header class="card-header" v-if="$slot.header"><slot name="header" />
</header>
其中的$slots
表示父组件传入插槽的对象。
通常用于手写渲染函数,但也可用于检测是否存在插槽。
每一个插槽都在 this.$slots
上暴露为一个函数,返回一个 vnode 数组,同时 key 名对应着插槽名。默认插槽暴露为 this.$slots.default
。
// this.$slots等于
{default: () => <div>...</div>,header: () => <div>...</div>,footer: () => <div>...</div>,
}
动态插槽名
插槽名不仅可以设置为常量,还可以设置为变量,如以下设置插槽名为变量mySlotName
:
<!-- Dialog.vue -->
<div class="dialog"><slot :name="mySlotName">默认底部</slot>
</div>
<Dialog><template #[mySlotName]><button>取消</button><button>确定</button></template>
</Dialog>
作用域插槽
上文中提到,插槽是在父组件中被定义的,所以无法读取到子组件的状态。那假如插槽需要拿到子组件状态该怎么办呢?
可以像对组件传递 props 那样,向插槽出口<slot>
上传入 attributes,实现把子组件的变量传递到插槽内容:
<!-- Dialog.vue -->
<div class="dialog"><slot msg="来自子组件的内容" /> <!-- 将子组件的状态传入slot -->
</div>
<Dialog v-slot="slotProps"> <!-- 设置一个slotProps变量接收来自父组件的插槽Props --><div>{{slotProps.msg}}</div>
</Dialog>
// 或
<Dialog v-slot="{msg}"><div>{{msg}}</div>
</Dialog>
最终渲染出的 DOM 是这样:
<div class="dialog"><div>来自子组件的内容</div>
</div>
具名作用域插槽
作用域插槽也可以与具名插槽混用,如下面的v-slot:header="slotProps1"
(也可写作#header="slotProps1
):
<!-- Dialog.vue -->
<div class="dialog"><header><slot name="header" msg="来自子组件的内容1" /></header><main><slot msg="来自子组件的内容2"/></main>
</div>
<Dialog><!-- header插槽 --><!-- v-slot设置在<template>上,而不是<Dialog>上 --><!-- v-slot:header="slotProps1" 也可以简写成 #header="slotProps1" --><template #header="slotProps1"> <div>来自父组件的内容</div><div>{{slotProps1.msg}}</div></template><!-- 默认插槽 --><!-- 支持在 v-slot 中使用解构 --><!-- 可写作 v-slot="{msg}" 或 v-slot:default="{msg}" 或 #default="{msg}" --><template #default="{msg}"><div>{{msg}}</div></template></Dialog>
最终渲染出的 DOM 是这样:
<div class="dialog"><header><div>来自父组件的内容</div><div>来自子组件的内容1</div></header><main><div>来自子组件的内容2</div></main>
</div>
再次推荐为默认插槽使用显式的 <template>
标签,不容易出错。
不允许像下面这样
同时在子组件上和template上定义v-slot,否则会编译报错。
<Dialog v-slot="slotProps2"><!-- header插槽 --><template v-slot:header="slotProps1"> <div>来自父组件的内容</div><div>{{slotProps1.msg}}</div></template><!-- 默认插槽 --><div>{{slotProps2.msg}}</div></Dialog>
高级列表组件示例
通过对具名作用域插槽的运用,我们可以实现一个高级列表组件,封装了可重用的逻辑 (如数据获取、分页、无限滚动等) 和视图输出,并将部分视图输出(如列表项的内容和样式、每页条目数)通过作用域插槽交给了消费者组件来管理。
<!-- FuncyList.vue -->
<ul><li v-for="item in list" key="item.id"><slot name="itemSlot" v-bind="item">{{item}}</slot></li>每页{{pageNum}}个
</ul>
<FuncyList :page-num="10" ><template #itemSlot="{id, title}"> <div>{{id}}-{{title}}</div></template></FuncyList>
无渲染组件
一些组件可能只包括了逻辑而不需要自己渲染内容,视图输出通过作用域插槽全权交给了消费者组件。我们将这种类型的组件称为无渲染组件。
<!-- NoRenderComponent.vue -->
<script>
export default {data() {return {pi: '3.1415926535'};},computed: {doublePi: () => this.pi *2;}
}
</script>
<template><!-- 仅包含逻辑,不包含任何视图 --><slot :text="doublePi" />
</temlate>
<NoRenderComponent ><template v-slot="{text}"><!-- 展示NoRenderComponent数据的视图写在插槽里 --><div class="uiClass">{{text}}</div></template>
</NoRenderComponent>