B站搜小满,跟着他的课程讲解学的,自己做一个记录,我觉得讲的很不错,先讲如何使用,再讲源码,讲的很细节,感觉可以反复学习。
1.一些插件,小妙招分享
- vscode中搜vue vscode snippets,可以自动生成代码模板,输入vb,找到自己所需要的模板点击即可
2.写之前的一些基础知识
1. ref和reactive的区别?
共同点:都是把一个变量变成一个响应式对象。
| 区别 | ref | reactive |
|---|---|---|
| 支持定义的类型 | 支持所有类型 | 引用类型(Array、Object...) |
| 使用 | 取值和赋值时都需要夹.value | 不需要 |
| 异步赋值时(后台请求参数赋值时) | 无 | 是proxy代理对象,直接赋值,就被覆盖,就无法响应式。解决方案:1.数组可以使用push解构 |
2. to系列
- toRef
将对象某一个属性,作为引用返回。 接收两个参数,第一个参数:对象,第二个参数:对象上的key
改变时:只能修改响应式的值,非响应的视图不变,值会改变
// 不使用toRef,这里的change点击后man的值是不会改变的
<script setup lang="ts">
import { reactive } from "vue";
let man = reactive({
name: "bob",
looks: {
hair: "black",
height: "1.78",
},
});
let name = man.name;
const change = () => {
name = "Jack";
console.log(man);
};
</script>
// 使用toRef,此时是可以改变man的name值
<script setup lang="ts">
import { ref, reactive, toRef } from "vue";
let man = reactive({
name: "bob",
looks: {
hair: "black",
height: "1.78",
},
});
let name = toRef(man, "name");
const change = () => {
name.value = "Jack";
console.log(man);
};
</script>
- toRefs
批量创建多个ref对象
使用方法
let man = reactive({
name: "bob",
looks: {
hair: "black",
height: "1.78",
},
});
const { name, looks } = toRefs(man);
const change = () => {
name.value = "Jack";
looks.value.hair = "brown";
console.log(man);
};
- toRaw
作用:将一个reactive生成的响应式对象转为普通对象
使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
3. computed
计算属性,具有缓存性,当依赖的属性的值发生改变时,才会重新计算,否则就会使用缓存中的值。
使用方式:选项式写法,支持一个对象传入get函数,通过set函数自定义操作
<template>
<div>
<div>姓:<input type="text" v-model="firstName" /></div>
<div>名:<input type="text" v-model="lastName" /></div>
<div>全名:{{ name }}</div>
<button @click="changeName">修改名称</button>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from "vue";
let firstName = ref<string>("张");
let lastName = ref<string>("三");
//选项式写法,支持一个对象传入get函数,通过set函数自定义操作
let name = computed({
get() {
return firstName.value + "~" + lastName.value;
},
set(newVal) {
[firstName.value, lastName.value] = newVal.split("~");
},
});
const changeName = () => {
name.value = "李~四";
};
</script>
使用方式二:函数式写法,只支持一个getter,不允许修改值
<template>
<div>
<div>姓:<input type="text" v-model="firstName" /></div>
<div>名:<input type="text" v-model="lastName" /></div>
<div>全名:{{ name }}</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from "vue";
let firstName = ref<string>("张");
let lastName = ref<string>("三");
// 函数式写法,只支持一个getter,不允许修改值
let name = computed(() => {
return firstName.value + "~" + lastName.value;
});
4. watch侦听器
作用:用来监听响应式数据的变化 写法
- 侦听一个数据
// 用来监听message的值
let message = ref<string>("小满");
// watch 的第一个参数就是数据源(监听的数据)
// 第二个参数callback回调函数,返回两个参数一个是新值一个是旧值
watch(message, (newVal, oldVal) => {
console.log("修改前的值为:" + oldVal);
console.log("修改后的值为:" + newVal);
});
- 侦听多个数据
// 当我们侦听多个数据源(变量)时,传入就需要是数组的形式,这里的回调函数返回的newVal,oldVal也是数组的形式返回的
// 用来监听message1和message2的值
let message1 = ref<string>("小满");
let message2 = ref<string>("大满");
// watch 的第一个参数就是数据源(监听的数据)
// 第二个参数callback回调函数,返回两个参数一个是新值一个是旧值
watch([message1,message2], (newVal, oldVal) => {
console.log("修改前的值为:" + oldVal);
console.log("修改后的值为:" + newVal);
});
- 监听对象时,深度监听
需要注意的是:
1.当侦听的是一个对象的时候,会发现oldVal和newVal是一样的
2.ref定义一个对象,进行监听的时候需要添加deep属性,reactive是不需要的。
let message = ref({
foo: {
bar: {
name: "小满",
},
},
});
// 当我们使用message.foo.bar.name这个属性时,想对它的修改进行监听,这样写是没办法监听的,所以需要添加deep属性
watch(message, (newVal, oldVal) => {
console.log(newVal,oldVal);
});
watch(
message,
(newVal, oldVal) => {
console.log(newVal,oldVal);
},
{
deep: true,
}
);
配置选项:
deep: 是否开启深度侦听
immediate: 是否立即执行
flush:"pre" pre组件更新之前调用,async 同步执行 post 组件更新之后执行
- 只侦听对象里的某个属性而不是整个对象
let message = ref({
foo: {
bar: {
name: "小满",
},
},
});
watch(
() => message.value.foo.bar.name,
(newVal, oldVal) => {
console.log(newVal, oldVal);
}
);
5. watchEffect 高级侦听器
接收一个回调函数,返回一个stop函数
特点:1.非惰性,一进入页面就会被调用
2.接收一个回调函数,是在数据监听到修改前会做的一些事情
import { ref, watchEffect } from "vue";
let message = ref<string>("hello");
let message2 = ref<string>("hi");
//onInvalidate 自定义的回调函数的名称
const stop = watchEffect((onInvalidate) => {
console.log(message.value);
console.log(message2.value);
onInvalidate(() => {
console.log("before");
// 页面进入时不会调用,但是监听到我们监听的message或message2发生改变时会调用,在console.log之前调用
// 这里我们可以做一些防抖等
});
});
// html按钮的点击事件,点击后就会停止监听
const stopBtn = () => { stop() }
配置选项: flush:
| pre | async | post | |
|---|---|---|---|
| 更新时机 | 组件更新前完成 | 强制效果始终同步触发 | 组件更新后执行 |
pre组件更新之前调用,async 同步执行 post 组件更新之后执行
当我们获取dom元素的时候,因为还没有加载所以获取不到结果为null,所以添加配置选项flush:post,这样就可以获取到dom元素了
watchEffect(
() => {
// 有一个id为ipt的input
let ipt: HTMLInputElement = document.getElementById(
"ipt"
) as HTMLInputElement;
console.log(ipt);
},
{
flush: "post",
}
);
6. vue3生命周期
生命周期:vue组件从创建-运行-销毁的整个过程
<template>
<div ref="strRef">{{ str }}</div>
<button @click="change">修改</button>
</template>
<script setup lang="ts">
import {
ref,
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
} from "vue";
// beforeCreate created setup语法糖模式是没有这两个声明周期的,setup代替
console.log("setup");
// 获取一个dom,可以发现onBeforeMount和onMounted之间的区别
// onBeforeMount是获取不到dom的,onMounted是可以获取到dom的
// onBeforeUpdate 获取的是更新之前的dom, onUpdated 获取的是更新之后的dom
// onBeforeUnmount onUnmounted
const str = ref<string>("hello");
const strRef = ref<HTMLDivElement>();
const change = () => {
str.value = "hi";
};
// 创建
onBeforeMount(() => {
console.log("创建之前========>", strRef.value);
});
onMounted(() => {
console.log("创建完成========>", strRef.value);
});
//更新
// 需要注意的是dom里面的值,而不是数据里面的前后值
onBeforeUpdate(() => {
console.log("更新组件之前========>", strRef.value?.innerText);
});
onUpdated(() => {
console.log("更新完成========>", strRef.value?.innerText);
});
// 销毁
onBeforeUnmount(() => {
console.log("销毁之前========>");
});
onUnmounted(() => {
console.log("销毁完成========>");
});
</script>
<style scoped></style>
7.scss+BEM
关于BEM可以去看这个 BEM
为什么要选择BEM
很多新手在开始写网页时,在命名方面可能都比较随心所欲。但是在一个正式的项目中,会有很多开发人员同时进行开发,如果每个开发人员都用自己的一套命名,这样会造成命名的识别度和一致性成为很大的问题,还会造成命名污染。这时使用BEM命名方法就可以很好的解决这个问题。
当然使用BEM还有很多其他的好处,例如每个块之间都是独立的,因此不会遇到层叠带来的问题。且这些块可以多次重用,可以减少必须维护的css代码量等。
el-table:是el的table元素
el-table__header: 是table组件的后代元素 表头
el-button--default
el-button--primary,分别就表示了按钮的不同状态。
npm install sass -D
创建一个bem.scss
$namespace:"zwt" !default;
// 表示这个变量没有赋值其他值的时候,就默认为zwt
$block-sel:"-" !default; //代表的是包裹我的容器。可理解为组件最外层元素
$ele-sel:"__" !default; //代表的是我是谁 ,组件的后代元素
$mod-sel:"--" !default; //代表的是我处于什么状态。
// 这个是定义block zwt-button
@mixin b($block){
$B:#{$namespace + $block-sel + $block};
.#{$B}{
// 相当于一个占位符,写的样式会覆盖进去
@content
}
}
// 定义ele zwt-table__header
@mixin e($el){
// & 可以获取父级类名
$selector:&;
// @at-root见scss文档7.4
@at-root{
#{$selector + $ele-sel + $el}{
@content
}
}
}
// mod zwt-button--primary
@mixin m($m){
// & 可以获取父级类名
$selector:&;
// @at-root见scss文档7.4
@at-root{
#{$selector + $mod-sel + $m}{
@content
}
}
}
在vue.config.ts中配置bem.scss全局使用
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
],
css:{
preprocessorOptions:{
scss:{
additionalData:`@import "./src/bem.scss";`
}
}
}
})
bem的简单使用
<template>
<div class="zwt-test">
zwt
<div class="zwt-test__inner">el</div>
<div class="zwt-test--success">test</div>
</div>
</template>
<script setup lang="ts"></script>
<style lang="scss">
@include b(test) {
color: red;
@include e(inner) {
color: blue;
}
@include m(success) {
color: green;
}
}
</style>
实现一个Layout布局
左侧menu最小宽度为200px, 右侧(header和content)自适应 header最小高度100px;content自适应
实现结果如下图
1.创建对应的文件夹
<template>
<div class="zwt-box">
<div><Menu></Menu></div>
<div class="zwt-box__right">
<Header></Header>
<Content></Content>
</div>
</div>
</template>
<script setup lang="ts">
import Menu from "./Menu/index.vue";
import Header from "./Header/index.vue";
import Content from "./Content/index.vue";
</script>
<style scoped lang="scss">
@include b(box) {
height: 100%;
overflow: hidden;
display: flex;
@include e(right) {
display: flex;
flex-direction: column;
flex: 1;
}
}
</style>
<template>
<div class="zwt-header">header</div>
</template>
<script setup lang="ts"></script>
<style scoped lang="scss">
@include b(header) {
height: 60px;
border-bottom: 1px solid #ccc;
}
</style>
<template>
<div class="zwt-menu">menu</div>
</template>
<script setup lang="ts"></script>
<style scoped lang="scss">
@include b(menu) {
min-width: 200px;
height: 100%;
border-right: 1px solid #ccc;
}
</style>
<template>
<div class="zwt-content">
Content
<div class="zwt-content__items" v-for="item in 100">{{ item }}</div>
</div>
</template>
<script setup lang="ts"></script>
<style scoped lang="scss">
@include b(content) {
flex: 1;
overflow: auto;
@include e(items) {
padding: 10px;
margin: 10px;
border: 1px solid #ccc;
border-radius: 4px;
}
}
</style>
8.父子组件传参
1. 父组件给子组件传参
父组件通过v-bind绑定数据,子组件通过defineProps接收传过来的值
示例:
<template>
<Children :arr="[1,2,3]" :msg="msg"></Children>
</template>
<script setup lang="ts">
let msg = "父组件传来的消息";
</script>
<template>
<div>
{{ msg }}
</div>
</template>
<script setup lang="ts">
// 接收父组件传过来的值
// 这里和vue2的接收不一样
const props = defineProps({
msg: {
type: String,
default: "默认值",
},
});
// 方法二 ts通过泛型的方式(ts代码提示更好)
const props = defineProps<{
msg: string;
arr: number[];
}>();
// 如果需要默认值 ts特有定义默认值 withDefaults
// 第一个参数:defineProps
// 第二个参数:对象,用定义默认值
const props = withDefaults(
defineProps<{
msg: string;
arr: number[];
}>(),
{
msg: "默认值",
arr: () => [222, 333],
}
);
// 代码中是无法直接使用msg的
console.log(props.msg);
console.log(msg); // 直接使用会报错
</script>
2.子组件传值给父组件
通过defineEmits
示例:
子组件
<button @click="send">传值给父组件</button>
...
const emit = defineEmits(["sendName"]);
const send = () => {
emit("send-name", "zwt");
};
父组件:
<Children :msg="msg" @send-name="getName"></Children>
...
const getName = (name: string) => {
console.log("子组件传过来的值=====>" + name);
};
写法二:ts特定写法
子组件
const emit = defineEmits<{
// 自定义事件名称 ,参数,返回值
// 可定义多个事件,通过emit去触发定义的事件
(e: "send-name", name: string): void;
}>();
const send = () => {
emit("send-name", "zwt");
};
3.子组件暴露方法/属性给父组件
子组件通过defineExpose暴露方法/属性
父组件通过ref获取
子组件:
const emit = defineEmits<{
// 自定义事件名称 ,参数,返回值
(e: "send-name", name: string): void;
}>();
const send = () => {
emit("send-name", "zwt");
};
父组件:
<template>
<Children ref="childrenRef"></Children>
</template>
<script setup lang="ts">
import { ref, onMounted } from "vue";
import Children from "./Children/index.vue";
const childrenRef = ref<InstanceType<typeof Children>>();
onMounted(() => {
// 在setup里面打印出来是undefined
// 我个人的理解是因为setup里子组件还没加载完成,所以会打印undefined
console.log(childrenRef.value?.name);
});
</script>
瀑布流案例
water-fall.vue 有对小满的案例进行了一些修改。增加了自适应,浏览器大小改变会重新生成瀑布流。
<template>
<div class="wraps">
<div
class="items"
v-for="item in waterFallList.arr"
:style="{
height: item.height + 'px',
width: item.width + 'px',
top: item.top + 'px',
left: item.left + 'px',
background: item.background,
}"
></div>
</div>
</template>
<script setup lang="ts">
import { onMounted, reactive, onUnmounted } from "vue";
const props = defineProps<{
list: any[];
}>();
let waterFallList = reactive<{
arr: any[];
}>({ arr: [] });
let heightList: number[] = [];
const init = () => {
waterFallList.arr = [];
heightList = [];
// 这个是每个div的宽度+间隔
const width = 130;
const vw = document.body.clientWidth;
// 每一行最大数,需要向下取整
const max = Math.floor(vw / width);
for (let i = 0; i < props.list.length; i++) {
// 先排列第一行
if (i < max) {
props.list[i].left = i * width;
props.list[i].top = 0;
waterFallList.arr.push(props.list[i]);
heightList.push(props.list[i].height);
} else {
let minHeight = heightList[0];
let index = 0;
heightList.forEach((item, i) => {
if (minHeight > item) {
minHeight = item;
index = i;
}
});
props.list[i].top = minHeight + 20;
props.list[i].left = index * width;
heightList[index] = heightList[index] + props.list[i].height + 20;
waterFallList.arr.push(props.list[i]);
}
}
};
onMounted(() => {
window.addEventListener("resize", init);
init();
});
onUnmounted(() => {
window.removeEventListener("resize", init);
});
</script>
<style scoped lang="scss">
.wraps {
position: relative;
width: 100%;
height: 100%;
overflow-y: auto;
.items {
position: absolute;
width: 120px;
}
}
</style>
父组件传过来的list
const list = [
{
height: 300,
background: "red",
},
{
height: 400,
background: "pink",
},
{
height: 500,
background: "blue",
},
{
height: 200,
background: "green",
},
{
height: 300,
background: "grey",
},
{
height: 400,
background: "#cc00ff",
},
{
height: 200,
background: "black",
},
{
height: 100,
background: "#996666",
},
{
height: 500,
background: "skyblue",
},
{
height: 200,
background: "#993366",
},
{
height: 100,
background: "#33ff33",
},
{
height: 400,
background: "skyblue",
},
{
height: 200,
background: "#6633CC",
},
{
height: 300,
background: "#666999",
},
{
height: 300,
background: "#66CCFF",
},
{
height: 300,
background: "skyblue",
},
{
height: 200,
background: "#CC3366",
},
{
height: 400,
background: "skyblue",
},
{
height: 200,
background: "#6633CC",
},
{
height: 500,
background: "skyblue",
},
{
height: 200,
background: "#993366",
},
{
height: 500,
background: "blue",
},
{
height: 200,
background: "green",
},
{
height: 300,
background: "#66CCFF",
},
{
height: 300,
background: "skyblue",
},
{
height: 300,
background: "grey",
},
{
height: 100,
background: "#996666",
},
{
height: 500,
background: "skyblue",
},
];
9. 组件的用法
例:封装一个卡片组件:包括标题、副标题、内容区域
<template>
<div class="card">
<header>
<div>标题</div>
<div>副标题</div>
</header>
<section>内容</section>
</div>
</template>
<script setup lang="ts"></script>
<style scoped lang="scss">
.card {
border: 1px solid #ccc;
width: 400px;
header {
display: flex;
justify-content: space-between;
padding: 5px;
border-bottom: 1px solid #ccc;
}
section {
min-height: 300px;
padding: 5px;
}
}
</style>
1.全局组件
出现频率较高的业务组件可以封装成全局组件。
如何全局引入呢?在miain.ts
import { createApp } from 'vue'
import './resize.css'
import App from './App.vue'
import cardVue from './components/card.vue';
export const app= createApp(App)
// 使用时的名称,引入的组件
app.component("Card",cardVue);
app.mount('#app')
在组件中就不需要import引入,直接使用就可以
如何批量注册全局组件?借鉴elementUI的Icon组件引入
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
const app = createApp(App)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
2.局部组件
一个页面拆分成多个组件引入。 比如Layout可以按需求分为:header,menu,main,footer等多个组件
局部组件引入,在需要使用的页面中引入
<template>
<card></card>
</template>
<script setup lang="ts">
import card from "./components/card.vue";
</script>
<style lang="scss">
#app {
height: 100%;
overflow: hidden;
}
</style>
3.递归组件
需要重复复用的组件,比如菜单组件等。
个人增加了一个递归,想实现的功能是,点击父节点被选中,那么子节点都要被选中,点击父节点取消,那么子节点都要取消选中。(应该还可以修改,但是目前想不到好的方法)
<template>
<div @click.stop="clickTap(item)" class="tree" v-for="item in data">
<input v-model="item.checked" type="checkbox" /><span>
{{ item.name }}
</span>
<TreeList
v-if="item.children && item.children.length > 0"
:data="item.children"
></TreeList>
</div>
</template>
<script setup lang="ts">
interface Tree {
name: string;
checked: boolean;
children?: Tree[];
}
defineOptions({
name: "TreeList",
});
const props = defineProps<{
data?: Tree[];
}>();
const clickTap = (item: Tree) => {
props.data?.forEach((i) => {
if (i.name === item.name) {
i.checked = !i.checked;
recursion(i, i.checked);
}
});
};
const recursion = (item: Tree, checked: boolean) => {
if (item.children && item.children.length > 0) {
if (checked) {
item.children.forEach((i) => {
i.checked = true;
recursion(i, true);
});
} else {
item.children.forEach((i) => {
i.checked = false;
recursion(i, false);
});
}
}
};
</script>
<style lang="scss" scoped>
.tree {
margin-left: 10px;
}
</style>
使用Tree组件
<template>
<Tree :data="data"></Tree>
</template>
<script setup lang="ts">
import { reactive } from "vue";
import Tree from "./components/Tree.vue";
interface Tree {
name: string;
checked: boolean;
children?: Tree[];
}
const data = reactive<Tree[]>([
{
name: "1",
checked: false,
children: [
{
name: "1-1",
checked: false,
},
],
},
{
name: "2",
checked: false,
},
{
name: "3",
checked: false,
children: [
{
name: "3-1",
checked: false,
children: [
{
name: "3-1-1",
checked: false,
},
{
name: "3-1-2",
checked: true,
},
],
},
],
},
]);
</script>
<style lang="scss">
#app {
height: 100%;
overflow: hidden;
}
</style>
10.动态组件
什么是动态组件? 多个组件使用同一个挂载点,并实现动态切换,这个就是动态组件。
如何实现? 在挂载点使用component标签,使用v-bing:is="组件"
比如tab页的切换:实现功能,点击切换组件
<template>
<div style="display: flex">
<div
@click="switchCom(item, index)"
:class="index == active ? 'active' : ''"
class="tabs"
v-for="(item, index) in data"
>
{{ item.name }}
</div>
</div>
<component :is="comId"></component>
</template>
<script setup lang="ts">
import { ref, reactive, markRaw } from "vue";
import AVue from "./components/A.vue";
import BVue from "./components/B.vue";
import CVue from "./components/C.vue";
const active = ref(0);
let comId = markRaw(AVue);
const data = reactive([
{
name: "A组件",
com: markRaw(AVue),
},
{
name: "B组件",
com: markRaw(BVue),
},
{
name: "C组件",
com: markRaw(CVue),
},
]);
const switchCom = (item: any, index: number) => {
active.value = index;
comId = item.com;
};
</script>
<style lang="scss">
.active {
background-color: skyblue;
}
.tabs {
border: 1px solid #ccc;
margin: 5px;
padding: 5px 10px;
}
</style>
11. 插槽
插槽就是在子组件中提供给父组件的一个占位符,父组件可以在占位符中填充任何代码。填充的内容会替换slot的位置。
示例:做一个dialog组件,引入后作为例子
<template>
<div class="dialog">
<header class="header"></header>
<main class="main"></main>
<footer class="footer"></footer>
</div>
</template>
1. 匿名插槽
<div class="dialog">
<header class="header"></header>
<main class="main">
<slot>我是匿名插槽的默认值,当插槽没有传值时,我会显示</slot>
</main>
<footer class="footer"></footer>
</div>
父组件中如何使用默认插槽
<Dialog>
<template v-slot>我是通过父组件插入的默认插槽</template>
</Dialog>
2. 具名插槽
<template>
<div class="dialog">
<header class="header">
<slot name="title">我是具名插槽的默认值,当插槽没有传值时,我会显示</slot>
</header>
<main class="main">
<slot>我是匿名插槽的默认值,当插槽没有传值时,我会显示</slot>
</main>
<footer class="footer">
<slot name="footer">我是具名插槽的默认值,当插槽没有传值时,我会显示</slot>
</footer>
</div>
</template>
父组件中如何使用具名插槽
<Dialog>
<template v-slot:title>我是通过父组件插入的具名插槽</template>
<template v-slot>我是通过父组件插入的默认插槽</template>
<!-- 具名插槽的两种写法 -->
<template #footer>脚标</template>
</Dialog>
注意:这里的slot顺序可以是随机的,具体展示会按子组件中的顺序展示
3. 作用域插槽
<div class="dialog">
<header class="header">
</header>
<main class="main">
<div v-for="item in data">
<!-- 这里的name,age,data也可以取别的名称 ,通过v-bind绑定-->
<slot
v-bind:userName="item.name"
v-bind:age="item.age"
:data="item"
></slot>
</div>
</main>
<footer class="footer">
</footer>
</div>
<script setup lang="ts">
import { reactive } from "vue";
type names = {
name: string;
age: number;
};
const data = reactive<names>([
{
name: "小满1",
age: 21,
},
{
name: "小满2",
age: 22,
},
{
name: "小满3",
age: 23,
},
{
name: "小满4",
age: 24,
},
{
name: "小满5",
age: 25,
},
]);
</script>
父组件中
<Dialog>
<!-- slotProps 的名字是可以任意取的,它是一个对象,包含了所有传递过来的数据。 -->
<template v-slot="slotProps"
>{{ slotProps.userName }} -- {{ slotProps.age }} -- {{ slotProps.data }}
</template>
<!-- 解构写法 -->
<!-- <template v-slot="{ userName, age, data }"
>{{ userName }} -- {{ age }} -- {{ data }}</template
> -->
</Dialog>
4. 动态插槽
父组件中,根据传过来的name(自定义变量名),插入不同的插槽中。如果name的值是default那么插入的就是默认插槽
<template>
<div>
<Dialog>
<template #[name]><div>动态插槽</div></template>
</Dialog>
</div>
</template>
<script setup lang="ts">
import Dialog from "./components/Dialog/index.vue";
import { ref } from "vue";
let name = ref("title");
</script>