热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Vue中slot的使用详解_vue.js

这篇文章主要介绍了Vue中slot的使用详解,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完

在Vue中,我们使用组件来组织页面和组织代码,类似于搭积木,每一个组件都是一个积木,使用一些相同或者不同组件就能搭建出我们想要的页面。

slot(插槽)是组件功能的重要组成部分,插槽必须用于组件才有意义。

它为组件提供了对外的接口,允许从组件外部传递内容,并将这部分内容放置到指定的位置。

使用 slot

当一个组件可能被使用至少两次并且两次使用内容(这里指组件视图的组成)不同时,插槽才有存在的必要。注意: 本文的代码都是基于Vue3编写。

基础用法

Link.vue



App.vue


视觉效果:

image.png

以上实现了两个组件Link.vue和App.vue,Link.vue是一个链接组件,在组件内部已经定义好了样式,然后链接的内容交由外界使用时填充。

在App.vue组件内则使用了Link.vue组件两次,并且两次传入的内容不同。

具名插槽

上面的Link.vue只要求填充一份内容,那么当我们需要在组件的好几个位置都填充不同的内容应该怎么办?这时候可以使用具名插槽,就是给组件的每个填充区域都取个名字,这样在使用的时候就可以往对应名字的那个区域填充内容。

Page.vue




App.vue 


效果图:

image.png

作用域插槽

为啥叫作用域插槽?首先要搞清楚作用域这个概念。在JS中,作用域表示的是当前的执行上下文,只有在当前作用域中变量才可以被使用。作用域有层次之分,分为父作用域和子作用域,子作用域可以访问父作用域中的变量,这一层层的往上则形成了作用域链。JS中只有全局作用域和函数作用域,ES6新增了块级作用域。关于作用域,这里不再赘言,有需要的同学可以去MDN作用域查看。

Vue本质上还是js,模板最终会被编译成render函数,每个组件都有一个render函数。下面先看个例子:

Count.vue


App.vue


效果图:

image.png

从上面的效果图中可以看到,在App.vue组件中使用Count.vue组件时,在Count.vue组件的插槽中,能够访问appCount变量,但是不能访问Count.vue组件的Count变量,这是为什么呢?理论上,插槽传入的内容最终会插入到Count.vue组件中,那么也应该可以访问Count.vue组件的变量才对啊?

父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。

上面的一段引用摘自Vue文档,这段文字表明了,在App.vue中的一切,包括Count.vue组件的插槽内容都是在App.vue组件下编译的,也就是Count.vue组件的插槽模板可以访问App.vue组件的所有变量,但不能访问Count.vue的任意变量。如果我一定要在插槽中访问Count.vue的count变量呢?这个时候作用域插槽就派上用场了。

作用域插槽允许在组件中对插槽所在的上下文暴露某一些变量,改写以上的Count.vue组件,

Count.vue




App.vue




这就是作用域插槽,本质上了是允许在父组件作用域访问到子组件作用域,它为插槽模板区域提供了一个数据来源于子组件的上下文。

作用域插槽的用处还是挺广的,总的来说当你需要它时自然会用到它,如果想提前学习,可以看一下elementUI的table组件。

slot 实现

上面就插槽的使用说了一大堆,关于插槽的实现还是没有涉及,下文讲解在Vue中插槽是如何实现的?

首先,我们都知道,无论是使用jsx还是模板,最终都会编译成render函数,并且render函数在执行之后会输出 Virtual Dom ,下面先看一个组件在编译完成之后是什么样子?

Comp.vue




App.vue




Comp.vue编译之后:

/* Analyzed bindings: {} */
import {
defineComponent,
ref
} from 'vue'
const __sfc__ = defineComponent((props) => {
const count = ref(0);
const OnClick= () => {
count.value++
}
return {
count,
onClick
}
})
import {
toDisplayString as _toDisplayString,
createElementVNode as _createElementVNode,
renderSlot as _renderSlot,
openBlock as _openBlock,
createElementBlock as _createElementBlock
} from "vue"
function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("div", null, [
_createElementVNode("p", null, "count: " + _toDisplayString(_ctx.count), 1 /* TEXT */ ),
_createElementVNode("button", {
onClick: _cache[0] || (_cache[0] = (...args) => (_ctx.onClick && _ctx.onClick(...args)))
}, " ADD "),
_renderSlot(_ctx.$slots, "default", {
count: _ctx.count
})
]))
}
__sfc__.render = render
__sfc__.__file = "Comp.vue"
export default __sfc__

App.vue编译之后:

/* Analyzed bindings: {} */
import {
defineComponent,
ref
} from 'vue'
import Comp from './Comp.vue'
const __sfc__ = defineComponent({
components: {
Comp
},
setup(props) {
const magRef = ref('当前的数字是')
return {
magRef
}
}
})
import {
toDisplayString as _toDisplayString,
createElementVNode as _createElementVNode,
resolveComponent as _resolveComponent,
withCtx as _withCtx,
createVNode as _createVNode,
openBlock as _openBlock,
createElementBlock as _createElementBlock
} from "vue"
function render(_ctx, _cache, $props, $setup, $data, $options) {
const _component_Comp = _resolveComponent("Comp")
return (_openBlock(), _createElementBlock("div", null, [
_createVNode(_component_Comp, null, {
default: _withCtx((slotProps) => [
_createElementVNode("p", null, _toDisplayString(_ctx.magRef) + ": " + _toDisplayString(slotProps.count), 1 /* TEXT */ )
]),
_: 1 /* STABLE */
})
]))
}
__sfc__.render = render
__sfc__.__file = "App.vue"
export default __sfc__

这里给大家推荐一个尤雨溪搞的测试网站Vue SFC Playground 可以直接看到组件编译之后的js代码。

image.png

这个编译是在加载.vue文件的时候就执行了,runtime阶段是不存在模板字符串了(使用UMD的时候会存在),在浏览器中执行的都是编译之后的js。下面具体分析一下以上Comp.vue和App.vue编译之后的js代码。

首先在Comp.vue中,会被编译成_renderSlot(_ctx.$slots, "default", {count: _ctx.count}),下面看看_renderSlot中干了什么?

export type Slot = (...args: any[]) => VNode[]
export type InternalSlots = {
[name: string]: Slot | undefined
}
export function renderSlot(
slots: Slots,
name: string,
props: Data = {},
// this is not a user-facing function, so the fallback is always generated by
// the compiler and guaranteed to be a function returning an array
fallback?: () => VNodeArrayChildren,
noSlotted?: boolean
): VNode {
let slot = slots[name]
openBlock()
const validSlotCOntent= slot && ensureValidVNode(slot(props))
const rendered = createBlock(
Fragment,
{ key: props.key || `_${name}` },
validSlotContent || (fallback ? fallback() : []),
validSlotContent && (slots as RawSlots)._ === SlotFlags.STABLE
? PatchFlags.STABLE_FRAGMENT
: PatchFlags.BAIL
)
return rendered
}

_renderSlot(_ctx.$slots, "default", {count: _ctx.count})这一句显然是执行_ctx.$slots.default({count: _ctx.count}),这说明在父组件中,每个插槽模板最终会被编译成一个函数,并且这个函数会被传递到子组件,在子组件里面会以props(这里是{count: _ctx.count})作为参数执行插槽函数,最终_ctx.$slots.default({count: _ctx.count})会返回virtual dom对象。

下面再看一下App.vue组件:




被编译成了:

_createVNode(_component_Comp, null, {
default: _withCtx((slotProps) => [
_createElementVNode("p", null, _toDisplayString(_ctx.magRef) + ": " + _toDisplayString(slotProps.count), 1 /* TEXT */ )
]),
_: 1 /* STABLE */
})

请忽略_withCtx,显然模板会编译成一个函数,并传递到子组件,进而在子组件中构建出完整的virtual dom, 上面中_ctx是当前组件的上下文,slotProps则是作用域插槽暴露的参数。

由此可以做一个总结,vue slot的实现原理:


  • 所有的模板会被编译成创建vnode的函数。

  • 父组件中传递给子组件的插槽(每个插槽都是一个函数,即名字不同的插槽为不同的函数)内容模板也会被编译成函数并且传递给子组件,模板中如果使用了父组件的变量,那么会通过闭包的形式在插槽函数中被使用。

  • 子组件在接收到父组件传递的插槽内容函数,会以在slot暴露的变量(只有作用域插槽有这些变量)为参数执行这个函数,返回vnode,这个vnode会作为子组件vnode的一部分。

总结

本文从使用和实现两个方面讲解了vue slot,有一定的深度,但忽略了一些使用和实现上的细节,有不足之处还请指出且谅解。 


推荐阅读
  • Vue3中setup函数的参数props和context配置详解
    本文详细介绍了Vue3中setup函数的参数props和context的配置方法,包括props的接收和配置声明,以及未通过props进行接收配置时的输出值。同时还介绍了父组件传递给子组件的值和模板的相关内容。阅读本文后,读者将对Vue3中setup函数的参数props和context的配置有更深入的理解。 ... [详细]
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文讨论了将HashRouter改为Router后,页面全部变为空白页且没有报错的问题。作者提到了在实际部署中需要在服务端进行配置以避免刷新404的问题,并分享了route/index.js中hash模式的配置。文章还提到了在vueJs项目中遇到过类似的问题。 ... [详细]
  • 本文涉及源码版本为2.6.9准备工作down一份Vue源码,从package.json入手,找我们需要的代码1、package.json中的scripts,build:nodesc ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • Linux重启网络命令实例及关机和重启示例教程
    本文介绍了Linux系统中重启网络命令的实例,以及使用不同方式关机和重启系统的示例教程。包括使用图形界面和控制台访问系统的方法,以及使用shutdown命令进行系统关机和重启的句法和用法。 ... [详细]
  • 本文介绍了一个Java猜拳小游戏的代码,通过使用Scanner类获取用户输入的拳的数字,并随机生成计算机的拳,然后判断胜负。该游戏可以选择剪刀、石头、布三种拳,通过比较两者的拳来决定胜负。 ... [详细]
  • 展开全部下面的代码是创建一个立方体Thisexamplescreatesanddisplaysasimplebox.#Thefirstlineloadstheinit_disp ... [详细]
  • 本文介绍了如何按需加载elementui的部分模块,以及如何设置覆盖某些属性。通过import引入Dialog模块,并使用Vue.component进行全局设置。同时使用Vue.use引入ElementUI和VueAxios模块。通过extends进行属性覆盖设置。 ... [详细]
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • 本文介绍了如何使用C#制作Java+Mysql+Tomcat环境安装程序,实现一键式安装。通过将JDK、Mysql、Tomcat三者制作成一个安装包,解决了客户在安装软件时的复杂配置和繁琐问题,便于管理软件版本和系统集成。具体步骤包括配置JDK环境变量和安装Mysql服务,其中使用了MySQL Server 5.5社区版和my.ini文件。安装方法为通过命令行将目录转到mysql的bin目录下,执行mysqld --install MySQL5命令。 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • 【Vue基础】监听属性watch
    Vue监听属性是watch,我们可以通过watch来响应数据的变化。代码示例: ... [详细]
  • 认识Vue关于Vue的描述有不少,不外乎都会拿来与Angular和React对比,同样头顶MVVM双向数据驱动设计模式光环的Angular自然被对比的最多,但到目前为止,Angul ... [详细]
author-avatar
郎郎2502918483
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有