Vue中神奇的插槽slot

遇到的问题?

在使用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>
显示结果

注意事项:

  1. 在slot标签添加样式无效
  2. 在父组件引用的子组件中也可以写入其他组件
  3. 如果子组件中有多个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

注意事项:

  1. 父组件连续给指定v-slot:footer赋值,后者会覆盖前者,v-slot:default也是一样
  2. 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步操作:

  1. user作为<slot>元素的一个attribute绑定上去, 绑定在<slot>元素上的attribute被称为插槽prop
<span>
  <slot v-bind:user="user">
    {{ user.lastName }}
  </slot>
</span>
  1. 在父级作用域中,我们可以使用带值的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官方文档连接

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353