想搞明白Vue3里slot咋用?别慌,这篇从基础到进阶给你掰碎了讲。不管是刚学Vue3的新手,还是想补插槽知识的同学,看完这些常见问题和例子,你对slot的理解肯定能更透~
插槽(Slot)是干啥的?Vue3里基础插槽咋写?
插槽本质是Vue里的「内容分发机制」——子组件留个“坑”,父组件往这个坑里塞内容,这样做能让组件更灵活,比如写个按钮组件,不同页面按钮里的文字、图标可能不一样,用插槽就能让父组件自由决定按钮里放啥。
Vue3里基础插槽(匿名插槽)写法特简单:
子组件(比如叫MyButton.vue
)里放<slot></slot>
,这就是留给父组件塞内容的位置,要是父组件没塞内容,<slot>
里还能写默认内容,比如<slot>默认按钮</slot>
。
父组件用的时候,直接在<MyButton>
标签里写内容就行:
<MyButton> <span style="color:red">我是自定义按钮文字</span> <img src="icon.png" /> </MyButton>
这样子组件的<slot>
位置就会被父组件塞的内容替换掉,要是父组件没写内容,就显示“默认按钮”~
多个插槽咋区分?具名插槽(Named Slots)咋用?
当子组件需要多个插槽时,光用匿名插槽不够用了,这时候得给插槽起名字,也就是「具名插槽」,比如写个页面布局组件Layout.vue
,需要header、主体、footer三个区域,每个区域都是插槽。
子组件里给<slot>
加name
属性:
<template> <div class="layout"> <header> <slot name="header"></slot> </header> <main> <slot name="main"></slot> </main> <footer> <slot name="footer"></slot> </footer> </div> </template>
父组件要给不同名字的插槽塞内容,得用<template>
配合v-slot
指令(简写),比如给header插槽塞内容:
<Layout> <template #header> <h1>这是页面标题</h1> </template> <template #main> <p>这里是正文内容...</p> </template> <template #footer> <p>© 2024 版权信息</p> </template> </Layout>
注意哦,v-slot:name
可以简写成#name
,比如#header
等价于v-slot:header
~ 这样每个具名插槽对应父组件里的<template>
内容,子组件里的位置就会被精准替换~
子组件数据想给父组件插槽用?作用域插槽(Scoped Slots)安排上!
有时候子组件里有数据,想让父组件在插槽里能用到这些数据,这时候就得用「作用域插槽」,举个例子:子组件TodoList.vue
里循环渲染todo列表,每个todo的内容是子组件里的数据,但父组件想自定义每个todo的样式(比如加个 checkbox、改文字颜色)。
子组件里,把数据通过slot的属性传出去:
<template> <ul> <li v-for="todo in todos" :key="todo.id"> <slot :todo="todo"></slot> <!-- 把todo数据传给父组件插槽 --> </li> </ul> </template> <script setup> import { ref } from 'vue' const todos = ref([ { id: 1, text: '学习Vue3', done: false }, { id: 2, text: '写代码', done: false } ]) </script>
父组件接收这些数据,得用v-slot
绑定一个变量(也能解构):
<TodoList> <template #default="slotProps"> <!-- default是匿名插槽的名字,也能省略#default --> <input type="checkbox" v-model="slotProps.todo.done" /> <span :style="{ color: slotProps.todo.done ? 'gray' : 'black' }"> {{ slotProps.todo.text }} </span> </template> </TodoList>
或者更简洁的解构写法:
<template #default="{ todo }"> <input type="checkbox" v-model="todo.done" /> <span :style="{ color: todo.done ? 'gray' : 'black' }"> {{ todo.text }} </span> </template>
这样父组件插槽里就能拿到子组件传的todo数据,自由定制渲染方式,是不是很灵活?
插槽也能动态切换?动态插槽名咋实现?
有时候插槽该显示哪个,得根据变量动态决定,这时候「动态插槽名」就派上用场了,Vue3里用v-slot:[变量名]
的语法就能实现。
举个例子:父组件有个变量currentSlot
,值可能是'left'
、'right'
,子组件里有对应的具名插槽<slot name="left">
和<slot name="right">
。
父组件代码:
<template> <script setup> import { ref } from 'vue' const currentSlot = ref('left') // 可以动态切换成'right' </script> <MyComponent> <template v-slot:[currentSlot]> <!-- 这里内容会根据currentSlot切换到对应具名插槽 --> {{ currentSlot === 'left' ? '左边内容' : '右边内容' }} </template> </MyComponent> </template>
子组件MyComponent.vue
:
<template> <div> <slot name="left">左边默认内容</slot> <slot name="right">右边默认内容</slot> </div> </template>
这样当currentSlot
变化时,父组件会自动切换给哪个具名插槽塞内容,子组件对应位置也会跟着变~ 动态场景下特别好用,比如tab切换、权限控制显示不同区域~
插槽的默认内容咋设置?不想传内容时能显示默认!
有时候父组件可能忘了给插槽传内容,这时候子组件里的<slot>
可以设置默认内容,避免页面空着不好看。
写法很简单:子组件里<slot>
标签中间写内容,就是默认内容,比如做个提示框组件Alert.vue
:
<template> <div class="alert"> <slot>这是默认提示文本</slot> </div> </template>
父组件用的时候,如果没写内容,就显示默认:
<Alert /> <!-- 显示“这是默认提示文本” -->
如果父组件传了内容,就替换默认:
<Alert> 这是自定义的提示内容~ </Alert>
机制,能让组件更健壮,就算父组件没传内容也不会崩,还能保持UI一致性~
插槽和组件通信有啥不一样?啥场景用slot更合适?
很多刚学Vue的同学会纠结:props/emit
也能传数据、传事件,为啥还要用slot?其实它们的「分工」不一样:
props/emit
:主打「数据和事件的通信」,父传子用props
,子传父用emit
,重点在「数据流转」。- 插槽(Slot):主打「内容和结构的分发」,父组件决定子组件里某块区域长啥样,重点在「UI定制」。
举几个适合用slot的场景:
- 布局组件:比如页面框架(header、footer、侧边栏),父组件自由决定每个区域的内容。
- 列表项自定义:像表格、下拉列表,子组件负责数据循环,父组件自定义每一项的渲染(比如表格列的内容、样式)。
- 通用组件扩展:比如弹窗组件,父组件决定弹窗里的表单、按钮怎么放;按钮组件,父组件决定按钮里的图标、文字组合。
举个实际例子:UI库的Table
组件,列的内容需要自定义,子组件Table
里循环渲染行,每行的列数据通过作用域插槽传给父组件,父组件用<template #column="{ row, column }">
来自定义每个单元格的内容(比如给姓名列加tooltip,给操作列加按钮),这种场景下,用插槽比props
传一堆渲染函数要直观太多~
再总结下:当你需要「让父组件决定子组件某部分长啥样」时,优先考虑slot;当你需要「父组件给子组件传数据/子组件通知父组件事件」时,用props/emit
~ 两者结合起来,组件的灵活性直接拉满~
现在再回头看,Vue3的slot其实就围绕「内容分发」展开:基础插槽管单区域,具名插槽管多区域,作用域插槽管子传父数据给插槽用,动态插槽管插槽名动态切换,默认内容管兜底,把这些用法和场景吃透,写组件时想咋灵活就咋灵活~ 要是练手的话,不妨从写个带插槽的按钮、布局组件开始,实操几次就熟啦~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。