遇到的问题?
在使用uni开发小程序或者App的时候,会遇到各种弹窗的需求,基本上都是一个黑色蒙版,上面中间有一块区域可以显示内容,不同的弹窗,只有中间这一块区域显示的内容是不同的,但是整个弹窗的展示和隐藏是可以抽取为一个组件的。为了解决这个需求,特此了解并学习记录了插槽slot
的使用。
Vue中的插槽slot?
- 是对组件的扩展,通过slot插槽向组件内部指定位置传递内容(先占位,然后父组件去写入内容),通过slot可以父子传参;
- 插槽显示与否、显示内容是由父组件来控制,而插槽在哪显示就由子组件来进行控制
怎么使用插槽?
1. 默认使用
父组件:
<template>
<div>
我是父组件
<slotOne1>
<p style="color:red">我是父组件插槽内容</p>
</slotOne1>
</div>
</template>
子组件:slotOne1
<template>
<div class="slotOne1">
<div>我是slotOne1组件</div>
<slot><p>我是默认显示的内容,父组件没有设置内容,那么我就会显示</p></slot>
</div>
</template>
显示结果
注意事项:
- 在slot标签添加样式无效
- 在父组件引用的子组件中也可以写入其他组件
- 如果子组件中有多个slot标签,那么内容也会被插入多次
2. 具名插槽
上面👆注意事项提到过《如果子组件中有多个slot标签(没有名字), 那么内容会被插入多次》,那我们想使用多个插槽,并且每个插槽有不同的内容,可以使用slot的一个attribute:name
,其实一个不带name的slot也会带有隐含的名字‘default’。
在向具名插槽提供内容的时候,我们可以使用v-slot
指令,并以v-slot
参数的形式提供其名称。
父组件:
<template>
<child-page>
<text>第一次没有指定名字的内容</text>
<template v-slot:header>
<text style="display: block;">我是父组件给的标题</text>
</template>
<template v-slot:footer>
<text style="display: block;">我是父组件第一次给的底部,不会显示</text>
</template>
<text>|第二次没有指定名字的内容</text>
<template v-slot:footer>
<text style="display: block;">我是父组件第二次给的底部,会显示</text>
</template>
</child-page>
</template>
子组件:
<template>
<view>
<text>这是child-page必显示的标题</text>
<slot name="header">
<text>header占位文本</text>
</slot>
<text style="display: block;">---我是中间的分割线---</text>
<slot></slot>
<slot name="footer"></slot>
</view>
</template>
- 具名插槽的缩写
跟v-on和v-bind一样,v-slot也有缩写,就是把v-slot:
替换为字符#
, 例如v-slot:default
为#default
, v-slot: header
为#header
。
注意事项:
- 父组件连续给指定
v-slot:footer
赋值,后者会覆盖前者,v-slot:default
也是一样 -
v-slot
指令只能添加在<template>
上(除了独占默认插槽的缩写语法,下面会介绍到)
3. 作用域插槽
3.1 编译作用域
这里要着重说一下编译作用域:父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。
比如:
<navigation-link url="/profile">
Logged in as {{ user.name }}
</navigation-link>
这个user.name肯定是在父组件的属性,也就是说这个插槽跟模板的其他地方一样可以访问相同的实例property(也就是相同的作用域),而不能访问<navigation-link>
的作用域。
3.2 作用域插槽
有时候我们需要让插槽内容能够访问子组件中才有的数据,例如,有一个带有如下模板的<current-user>
组件:
<span>
// 父组件不指定内容,默认会显示 user.lastName
<slot>{{ user.lastName }}</slot>
</span>
我们在父组件中,可能想换掉备用内容,用名非姓来显示:
<current-user>
{{ user.firstName }}
</current-user>
当然,这样的代码是不会正常工作的,因为只有<current-user>组件可以访问到user而我们提供的内容是在父级渲染的,具体可以看👆 3.1的介绍。
为了让user
在父级的插槽内容中可以访问到,我们需要2步操作:
- 将
user
作为<slot>
元素的一个attribute绑定上去, 绑定在<slot>元素上的attribute被称为插槽prop:
<span>
<slot v-bind:user="user">
{{ user.lastName }}
</slot>
</span>
- 在父级作用域中,我们可以使用带值的v-slot来定义我们提供的插槽prop的名字:
<current-user>
<template v-slot:default="slotProps(这个是可以随意命名的)">
{{ slotProps.user.firstName }}
</template>
</current-user>
3.3 独占默认插槽的缩写语法
在上述情况下,当被提供的内容只有默认插槽时,组件的标签才可以被当做插槽的模板来使用,就是可以把v-slot
直接用在组件上,可以跟上面对比一下代码的不同:
<current-user v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</current-user>
你认为这就是最简单了吗?当然不是。。。
<current-user v-slot="slotProps">
{{ slotProps.user.firstName }}
</current-user>
我们把default去掉,不带参数的v-slot
被假定对应默认插槽。
注意:默认插槽的缩写语法不能和具名插槽混用,因为它会导致作用域不明确,需要为所有的插槽使用完整的基于
<template>
的语法.
错误❎的代码:
<!-- 无效,会导致警告 -->
<current-user v-slot="slotProps">
{{ slotProps.user.firstName }}
<template v-slot:other="otherSlotProps">
slotProps is NOT available here
</template>
</current-user>
正确✅的代码:
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
<template v-slot:other="otherSlotProps">
...
</template>
</current-user>
3.4 解构插槽Prop
首先,我们要明白一下:作用域插槽的内部工作原理是将你的插槽内容包括在一个拥有单个参数的函数里:
function (slotProps) {
// 这里是插槽内容,我们可以直接使用slotProps
}
so,意味着v-slot
的值实际上可以是任何能够作为函数参数的JavaScript表达式。
I think : 这只是一个锦上添花的功能,可以让模板更加简洁。
大概了解一下吧,在支持的环境下(单文件组件或现代浏览器),可以使用ES2015解构来传入具体的插槽prop,如下:
- 让模板更简洁
<current-user v-slot="{ user }">
{{ user.firstName }}
</current-user>
- 将prop重命名
<current-user v-slot="{ user: person }">
{{ person.firstName }}
</current-user>
- 定义默认显示内容
<current-user v-slot="{ user = { firstName: 'Guest' } }">
{{ user.firstName }}
</current-user>
4. 动态插槽名 2.6.0新增
动态指令参数也可以用在v-slot
上,来定义动态的插槽名:
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
</base-layout>
5. 其他示例
插槽prop允许我们将插槽转换为可复用的模板,这些模板可以基于输入的prop渲染除不同的内容。这在设计封装数据逻辑同时允许父级组件自定义部分布局的可复用组件是最有用的。
例如,我们要实现一个 <todo-list> 组件,它是一个列表且包含布局和过滤逻辑:
<ul>
<li
v-for="todo in filteredTodos"
v-bind:key="todo.id"
>
{{ todo.text }}
</li>
</ul>
我们可以将每个todo作为父级组件的插槽,以此通过父级组件对其进行控制,然后将todo作为一个插槽prop进行绑定,这样可以在不同的父级组件中,对todo数据进行不同的展示:
<ul>
<li
v-for="todo in filteredTodos"
v-bind:key="todo.id"
>
<!--
我们为每个 todo 准备了一个插槽,
将 `todo` 对象作为一个插槽的 prop 传入。
-->
<slot name="todo" v-bind:todo="todo">
<!-- 后备内容 -->
{{ todo.text }}
</slot>
</li>
</ul>
现在当我们使用 <todo-list> 组件的时候,我们可以选择为 todo 定义一个不一样的template作为替代方案,并且可以从子组件获取数据:
<todo-list v-bind:todos="todos">
<template v-slot:todo="{ todo }">
<span v-if="todo.isComplete">✓</span>
{{ todo.text }}
</template>
</todo-list>
只是为了学习&记录&自己的一点理解,附上:
Vue.js官方文档连接。
网友评论