vue3的一些学习

477 阅读14分钟

B站搜小满,跟着他的课程讲解学的,自己做一个记录,我觉得讲的很不错,先讲如何使用,再讲源码,讲的很细节,感觉可以反复学习。

1.一些插件,小妙招分享

  1. vscode中搜vue vscode snippets,可以自动生成代码模板,输入vb,找到自己所需要的模板点击即可

image.png

image.png

2.写之前的一些基础知识

1. ref和reactive的区别?

共同点:都是把一个变量变成一个响应式对象。

区别refreactive
支持定义的类型支持所有类型引用类型(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>

image.png

  • 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:

preasyncpost
更新时机组件更新前完成强制效果始终同步触发组件更新后执行

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组件从创建-运行-销毁的整个过程

image.png

<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

scss中文网

关于BEM可以去看这个 BEM

为什么要选择BEM

很多新手在开始写网页时,在命名方面可能都比较随心所欲。但是在一个正式的项目中,会有很多开发人员同时进行开发,如果每个开发人员都用自己的一套命名,这样会造成命名的识别度和一致性成为很大的问题,还会造成命名污染。这时使用BEM命名方法就可以很好的解决这个问题。

当然使用BEM还有很多其他的好处,例如每个块之间都是独立的,因此不会遇到层叠带来的问题。且这些块可以多次重用,可以减少必须维护的css代码量等。

image.png el-table:是el的table元素

el-table__header: 是table组件的后代元素 表头

image.png 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自适应

实现结果如下图 image.png 1.创建对应的文件夹 image.png

<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页的切换:实现功能,点击切换组件

image.png

<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>