什么是插槽

插槽(slot)通俗的理解就是“占坑”,在组件模板中占有位置,当使用该组件的时候,可以指定各个坑的内容。也就是我们常说的内容分发

值得一提的是,插槽这个概念并不是 Vue 提出的,而是 web Components 规范草案中就提出的,具体入门可以看 使用 templates and slots[1]Vue 只是借鉴了这个思想罢了

在 Vue 2.6.0 中,我们为具名插槽和作用域插槽引入了一个新的统一的语法 (即 v-slot 指令)。它取代了 slotslot-scope,这两个目前已被废弃但未被移除且仍在文档中的 attribute

本文的例子基于 Vue 2.6.X,所以用的都是 v-slot 的语法。

本文 DEMO 已全部放到 Github[2] 和 沙箱[3] 中,供大家学习,如有问题,欢迎评论提出。

默认插槽

我们新建父组件 Parent 和子组件 Child,结构如下:

父组件:

<h3>默认插槽h3><Child>    <div class="parent-text">Hi, I am from parent.div>Child>

子组件:

<div class="child">    <slot>slot>    <div>Hello, I am from Child.div>div>

父组件调用 Child 组件的时候,会在 Child 标签中将内容传入到子组件中的 标签中,如下所示


也就是最后的渲染结果如下:

<div class="child">    <div class="parent-text">Hi, I am from parent.div>    <div>Hello, I am from Child.div>div>

后备内容

我们可以在子组件中的 中加入一些内容,像下面一样

<div class="child">  <slot>当父组件不传值的时候,我就展示,我只是一个后备军slot>  <div>Hello, I am from Child.div>div>

当父组件调用的时候, 子组件标签内没有相关的内容时候, 标签内的内容就会生效,否则就不会渲染,可以理解就是个“备胎”

如父组件调用上面子组件:

        <h3>后备内容h3>    <Child1>Child1>

结果如下:

具名插槽

当然,插槽可以不止一个,这个主要是为了能够灵活的控制插槽的位置以及组件的抽象。我们可以通过在子组件的 slot 标签中设置 name 属性,然后在父组件中通过 v-slot:(或者使用简写 #) + 子组件 name 属性值的方式指定要插入的位置。如果是默认插槽的话,v-slot:default 即可

如下父组件:

        <h3>具名插槽h3>    <Child2>      <template v-slot:footer><div>我是底部div>template>      <template #header><div>我是头部div>template>      <template v-slot:default>        <div>我是内容div>      template>    Child2>

子组件

  <div class="child">    <slot name="header">slot>    <slot>slot>    <div>Hello, I am from Child.div>    <slot name="footer">slot>  div>

需要留意的是,最后渲染的顺序是以子组件的顺序为主,也就是上面的例子,渲染出来如下:



作用域插槽

有时候,我们想在一个插槽中使用子组件的数据和事件,类似如下(注意:user 是定义在 Child3 组件中的数据):

    <Child3>      <template>        <div>我的名字:{{user.name}}div>        <div>我的年龄:{{user.age}}div>        <button @click="callMe">Clicl Mebutton>      template>    Child3>

会直接报错:

原因在于父组件取不到子组件的数据,这里记住一个原则:父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。

那我们怎样才能获取到子组件的数据或者事件呢?我们可以直接在子组件中通过 v-bind 的方式将数据或者事件传递给父组件中,如下所示

  <div class="child">    <div>Hello, I am from Child.div>

    <slot :user="user" :callMe="callMe">slot>  div>

然后在父组件中的插槽内,通过类似 v-slot:default="slotProps" 接受子组件传递过来的数据

    <Child3>

      <template v-slot:default="slotProps">        <div>我的名字:{{slotProps.user.name}}div>        <div>我的年龄:{{slotProps.user.age}}div>        <button @click="slotProps.callMe">Clicl Mebutton>      template>    Child3>

以上 slotProps 可以自定义,而且可以使用解构赋值的语法

<template v-slot:other="{ user, callMe}">  <div>我的名字:{{user.name}}div>  <div>我的年龄:{{user.age}}div>  <button @click="callMe">Clicl Mebutton>template>

实例:解耦业务逻辑和视图

我们经常会遇到一个场景,就是两个组件的业务逻辑是可以复用的,但是视图却不一样,比如我们经常会有类似切换开关的需求,功能包括:

  • 关闭开关
  • 打开开关
  • 切换开关
  • 开关关闭或者打开的时候不一样的内容

我们可以很快的写出它的一个 JS 业务逻辑代码:

export default {  data() {    return {      currentState: this.state    }  },  props: {    state: {      type: Boolean,      default: false    }  },  methods: {    openState() {      this.currentState = true;    },    closeState() {      this.currentState = false;    },    toggle() {      this.currentState = !this.currentState;    }  }}

但是可能现在我的样式一是这样的


然而另外一个地方的样式是这样的(只是举个例子,现实可能更加的复杂,甚至有可能一些按钮直接就隐藏掉了)


这个时候,插槽就派上了用场。上面提到作用域插槽可以将数据和事件从子组件传递给父组件,这就相当于对外暴露了接口。而且可以将 HTML 中的 DOM 以及 CSS 交给父组件(调用方)去维护,子组件通过 标签插入的位置即可,主要逻辑如下:

子组件:

<template>  <div class="toggle-container">    <slot :currentState="currentState" :setOn="openState" :setOff="closeState" :toggle="toggle">slot>  div>template>

父组件:

<Toggle1 :state="state" class="toggle-container-two">  <template v-slot:default="{currentState, setOn, setOff, toggle }">    <button @click="toggle">切换button>    <button @click="setOff">关闭button>    <button @click="setOn">打开button>    <div v-if="currentState">我是打开的内容div>    <div v-else>我是关闭的内容div>  template>Toggle1>

我们现在采用的是单文件的方式书写的,实际上子组件还是会有相关的 HTML 结构,如何做到子组件完全不需要渲染自己的 HTML 呢?那得了解下无渲染组件的实现

进阶:无渲染组件的实现

无渲染组件(renderless components)是指一个不需要渲染任何自己的 HTML 的组件。相反,它只管理状态和行为。它会暴露一个单独的作用域,让父组件或消费者完全控制应该渲染的内容。Vue 中,提供了单文件组件的写法。像上面的示例一样,我们始终还是在子组件中进行了一些渲染的操作,那如何做到真正的不渲染组件呢?

比如上面的 toggle 例子,我们已经做到了子组件暴露一个单独的作用域,让父组件或消费者完全控制应该渲染的内容。现在我们需要将单文件中的 template 结构(slot 标签外层的 div)完全交给父组件,但单文件组件中 slot 标签是不能作为 template 的根元素的

这个时候,我们需要了解一下 Vue 渲染函数(render function)

归根结底,Vue 及其所有的组件都只是 JavaScript。单文件组件最后会被构建工具,如 webpack,将 CSS 抽取形成一个文件,其他的内容会被转换成  JavaScript,类似如下:

export default {  template: <div class="mood">...div>,  data: () => ({ todayIsSunny: true })}

当然,这个不是它的最终形态,模板编译器会提取 template 属性内容并将其内容编译为 JavaScript,然后通过 render 函数添加到组件对象中。最终形态应该是如下:

render(h) {  return h(    'div',    { class: 'mood' },    this.todayIsSunny ? 'Makes me happy' : 'Eh! Doesn't bother me'  )}

具体的渲染函数可参见官网[4],虽然写 render 函数的成本会高一些,但是它的性能会比单文件组件好很多。

以上的例子,只有插槽的时候,我们只需要在 render 函数中,使用 this.$scopedSlots.default 代替掉 标签即可

代码如下:

export const toggle = {  data() {    return {      currentState: this.state    }  },  render() {    return this.$scopedSlots.default({      currentState: this.currentState,      setOn: this.openState,      setOff: this.closeState,      toggle: this.toggle,    })  },  props: {    state: {      type: Boolean,      default: false    }  },  methods: {    openState() {      this.currentState = true;    },    closeState() {      this.currentState = false;    },    toggle() {      this.currentState = !this.currentState;    }  }}

以上就可以做到子组件完全不渲染自己的 HTML

总结

本文介绍了一些 Vue 插槽的基本知识,包括

  • 默认插槽
  • 后备内容
  • 具名插槽
  • 作用域插槽

然后介绍了一下,如何通过插槽实现业务逻辑和视图的解耦,再结合渲染函数实现真正的无渲染函数

本文 DEMO 已全部放到 Github[5] 和 沙箱[6] 中,供大家学习,如有问题,可以评论提出。

这么用心了,求个赞,哈哈

希望对大家有所帮助~

往期优秀文章推荐

  • 【Vue进阶】——如何实现组件属性透传?[7]
  • 前端应该知道的 HTTP 知识【金九银十必备】[8]
  • 最强大的 CSS 布局 —— Grid 布局[9]
  • 如何用 Typescript 写一个完整的 Vue 应用程序[10]
  • 前端应该知道的web调试工具——whistle[11]

参考:

  • Vue 插槽(slot)使用(通俗易懂)[12]

  • vue 2.6 中 slot 的新用法[13]

  • (译)函数式组件在Vue.js中的运用[14]

  • Building “Renderless” Vue Components[15]

参考资料

[1]

使用 templates and slots: https://developer.mozilla.org/zh-CN/docs/Web/Web_Components/Using_templates_and_slots

[2]

Github: https://github.com/GpingFeng/vue-slot

[3]

沙箱: https://codesandbox.io/s/hopeful-nash-id826?file=/src/main.js

[4]

官网: https://cn.vuejs.org/v2/guide/render-function.html

[5]

Github: https://github.com/GpingFeng/vue-slot

[6]

沙箱: https://codesandbox.io/s/hopeful-nash-id826?file=/src/main.js

[7]

【Vue进阶】——如何实现组件属性透传?: https://juejin.im/post/6865451649817640968

[8]

前端应该知道的 HTTP 知识【金九银十必备】: https://juejin.im/post/6864119706500988935

[9]

最强大的 CSS 布局 —— Grid 布局: https://juejin.im/post/6854573220306255880

[10]

如何用 Typescript 写一个完整的 Vue 应用程序: https://juejin.im/post/6860703641037340686

[11]

前端应该知道的web调试工具——whistle: https://juejin.im/post/6861882596927504392

[12]

Vue 插槽(slot)使用(通俗易懂): https://juejin.im/post/6844903920037281805

[13]

vue 2.6 中 slot 的新用法: https://juejin.im/post/6844903885476200461

[14]

(译)函数式组件在Vue.js中的运用: https://juejin.im/post/6844903752164442120

[15]

Building “Renderless” Vue Components: https://css-tricks.com/building-renderless-vue-components/

div滚动到指定位置 vue_【Vue 进阶】从 slot 到无渲染组件相关推荐

  1. div滚动到指定位置 vue_Vue列表实现滚动到指定位置样式改变

    这个需求大概是这样子: 我做的一个聊天Demo,在搜索框搜索用户,可以滚动到指定的用户.然后成选中状态. 这是目前状态,我搜索南宫仆射 ,想要下面的用户列表直接滚动到 南宫仆射 并改变CSS样式. 查 ...

  2. js是滚动条滑到固定位置_js滚动到指定位置

    序言:在网络上百度,关键字:"js div滚动到指定位置",结果基本上大同小异!各种大神都给我们总结出来了四种滚动到指定位置的办法,可惜再下愚钝,每个都不会用,所以写了一个超级简单 ...

  3. vue 点击div 获取位置_vue 点击元素滚动到指定位置

    {{item}} {{item}} data() {return{ navgator: ['列表A','列表B','列表C','列表D', ], navgatorIndex:0, listBox: [ ...

  4. DIV滚动条滚动到指定位置(jquery的position()与offset()方法区别小记)

    相对浏览器,将指定div滚到到指定位置,其用法如下 $("html,body").animate({scrollTop: $(obj).offset().top},speed); ...

  5. vue 网页滚动到指定位置显示动画效果

    大部分的网页,在滚动到一定位置的时候,当前区块的文案或者图片有一些飞入,淡入淡出,向上划入的动画效果 //向上划入样式 .boxUp {transform: translateY(0%) !impor ...

  6. Query实现将div中滚动条滚动到指定位置的方法,html中offsetTop、clientTop、scrollTop、offsetTop各属性的解释

    TML精确定位:scrollLeft,scrollWidth,clientWidth,offsetWidth  scrollHeight: 获取对象的滚动高度.  scrollLeft:设置或获取位于 ...

  7. 页面滚动到指定位置导航栏固定顶部

    做一个网页时经常会用到导航栏,导航栏对于一个网站来说,地位是举足轻重的,在用到导航栏时,我们经常会用到一个效果,就是当页面滑动到一定的位置时,导航栏需要固定在页面的顶部,这是怎么实现的呢? 下面的代码 ...

  8. 用startSmoothScroll实现RecyclerView滚动到指定位置并置顶,含有动画。

    RecyclerView滚动到指定位置并置顶 RecyclerView本身提供了几个定位的方法,除了手动滑动的scrollTo,smootScrollTo和scrollBy,smoothScrollB ...

  9. 监听某个区域滚动_监听页面滚动及滚动到指定位置

    一.原生js通过window.onscroll监听 window.onscroll = function() { //为了保证兼容性,这里取两个值,哪个有值取哪一个 //scrollTop就是触发滚轮 ...

最新文章

  1. python制作缩略图
  2. Java基础:网络编程
  3. UA MATH523A 实分析3 积分理论例题 Fubini定理证明积分不等式
  4. RedHat 下常见系统故障及恢复方法(整理)
  5. AM-资产冻结与停用
  6. Automatic Exploit Generation:漏洞利用自动化
  7. 当计算机从硬盘读取数据后 将数据,当计算机从硬盘读取数据后,将数据暂时储存在于()...
  8. idea中配置maven仓库
  9. finally中关闭资源
  10. 细说Redirect重定向请求(情节分享)
  11. OpenWrt常用命令总结
  12. 捷联惯导系统学习7.1(捷联惯导粗对准 )
  13. Ubuntu下mysql可视化_ubuntu上mysql有可视化界面吗 ubuntu mysql 图形界面
  14. 小小串联电阻,大大的作用
  15. linux jq下载文件,linux 之 jq
  16. Web项目中手机注册短信验证码实现的全流程及代码
  17. echarts中改变饼图的颜色
  18. 2017百度之星资格赛—1003度度熊与邪恶大魔王
  19. 性能提升30倍丨基于 DolphinDB 的 mytt 指标库实现
  20. 中国古代数学家张丘建在他的《算经》中提出了一个著名的 “ 百钱买百鸡问题”鸡翁一, 值钱五,鸡母一,值钱三,鸡雏三,值钱一,百钱买百鸡,问翁、母、雏各几何

热门文章

  1. 第八次课作业(采购管理、信息与配置管理)
  2. IOSday01 连线和程序标识
  3. Arduino初初教程10——温湿度传感器
  4. 服务器无法通过系统非页面共享区来进行分配,因为共享区当前是空的解决办法.供参考....
  5. apache 下实现防盗链
  6. 100M独享带宽香港服务器有多快
  7. 框架源码系列四:手写Spring-配置(为什么要提供配置的方法、选择什么样的配置方式、配置方式的工作过程是怎样的、分步骤一个一个的去分析和设计)...
  8. 【Python问题解决】关于解决python3.x无法使用PIL库的解决方法
  9. 它们的定义app.config中间section节点和在执行中使用
  10. js 光标移动到输入框最后位置函数