Vue3-Day3
1. 插槽
插槽就是子组件预留的占位坑位,让父组件可以自定义插入 HTML / 组件内容,实现组件内容灵活复用。
简单比喻:
子组件是一个相框,<slot> 就是相框中间空白区域;父组件传入图片 / 文字,填进这个空白位置。
在Vue中,有三种插槽,对应不同的使用方式
- 默认插槽(匿名插槽):该插槽一个组件只能存在一个
- 具名插槽:子组件多处需要自定义内容,给每个 slot 起名字,父组件对应 v-slot:名字(简写 #名字)。
- 作用域插槽:
- 普通插槽:父只能把内容传给子;
- 作用域插槽:子组件内部数据,反向传给父组件使用。
- 适用场景:表格、列表循环,子循环列表,父自定义每一行渲染。
子组件:给 slot 绑定自定义属性向外抛数据
子组件Card
<!-- components/Card.vue --> <template> <div class="card"> <h1> <!-- 默认插槽:一个组件只有一个默认插槽 --> <slot></slot> </h1> <h2> <!-- 具名插槽:需要多个插槽可以取名字 --> <slot name="name1"></slot> </h2> <h2> <slot name="name2"></slot> </h2> </div> </template> <script setup> </script>子组件List
<template> <div> <div v-for="item in list" :key="item.id"> <!-- 子给插槽传数据:item ,别名row --> <!-- 作用域插槽 --> <slot :row="item"></slot> </div> </div> </template> <script setup> const list = [ { id:1, name:'张三' }, { id:2, name:'李四' } ] </script>父组件
<script setup lang="ts"> import Card from './Card.vue' import List from './List.vue' </script> <template> <div> 父组件 <Card> <p>父组件使用子组件默认插槽</p> <template #name1> <p>父组件使用子组件具名插槽1</p> </template> <template #name2> <p>父组件使用子组件具名插槽2</p> </template> </Card> </div> <div> <List> <!-- 父组卷使用子组件作用域插槽 --> <template #default="{ row }"> <div>{{ row.id }} - {{ row.name }}</div> </template> </List> </div> </template>2. defineOptions
- 版本要求
Vue ≥ 3.3 / Vite / Vue CLI 新版内置支持,无需导入,直接使用宏
作用:在<script setup>内声明组件选项(name、inheritAttrs、props、emits 等) - 注意
- 在Vite中,会自动命名name,但是推荐还是要写,不然会出现小问题
- 命名了name后在Vue DevTools中可以看到自定义名称,而非匿名组件
常用于定义组件name
<script setup> // 直接定义组件名称,无需额外script块 defineOptions({ name: 'UserCard' }) </script> <template> <div>用户卡片</div> </template>所有选项
<script setup> defineOptions({ // 组件名称(最重要,用于递归组件、devtools、keep-alive) name: 'TableList', // 是否继承父组件非props属性 inheritAttrs: false, // 自定义渲染函数(极少用) render: () => {}, // 静态props、emits(setup内优先用defineProps/defineEmits) props: [], emits: [], // 其他原生选项:components、directives 等不推荐在这里写 }) </script>3. 动态组件
- 作用:同一位置切换多个组件,不用 v-if/v-else 一堆判断,通过 is 属性指定要渲染的组件。
- 每次切换组件
- 旧组件执行 unmounted 销毁
- 新组件重新 mounted 创建
- 表单输入、滚动位置、接口数据全部丢失
- 想要保留状态,就套上
<KeepAlive>。
3.1 实现组件切换
父组件 - Tab0
<script setup lang="ts"> import { ref } from 'vue' import Tab1 from './Tab1.vue' import Tab2 from './Tab2.vue' defineOptions({ name: 'Tab0' // 组件名称 }) // 初始值 const current = ref(Tab1); </script> <template> <button @click="current = Tab1">切换页面1</button> <button @click="current = Tab2">切换页面2</button> <!-- 通过:is绑定组件 --> <component :is="current" /> </template>子组件 - Tab1
<script setup lang="ts"> defineOptions({ name: 'Tab1' }) </script> <template> <P>我是子组件1</P> </template>子组件 - Tab2
<script setup lang="ts"> defineOptions({ name: 'Tab2' }) </script> <template> <P>我是子组件2</P> </template>3.2 实现组件切换保留缓存
<keepAlive>可以实现缓存不活动的组件实例,不销毁,再次切换时直接复用,保留:表单值、滚动位置、接口缓存数据、定时器等。
基础用法(包裹动态组件):此时切换 Tab1 / Tab2,数据不会重置。
<KeepAlive> <component :is="current" /> </KeepAlive>include(依赖组件 name):只缓存 TabOne、TabTwo,其他组件 每次切换销毁重建
<KeepAlive include="TabOne,TabTwo"> <component :is="current" /> </KeepAlive>exclude:排除指定 name,不缓存
<KeepAlive exclude="TabTwo">max:最大缓存实例数,超出数量后,最久没使用的组件自动销毁
<KeepAlive max="5">子组件中缓存专属生命周期钩子(只有被 KeepAlive 缓存才触发):被缓存的组件切换时不会执行 onMounted / onUnmounted,只会走 activated / deactivated。
<script setup> import { onActivated, onDeactivated, onMounted } from "vue" // 只会首次创建执行 onMounted(() => { console.log("组件创建") }) // 每次切进来都执行 onActivated(() => { console.log("组件激活,刷新列表/重置查询条件") }) // 每次切走执行 onDeactivated(() => { console.log("组件失活,清除定时器") }) </script>路由场景最常用(页面缓存)
<KeepAlive include="UserList,OrderPage"> <router-view /> </KeepAlive>踩坑点:Vue Router4 内部重构了 渲染机制,路由视图是动态异步组件,不能直接作为 的子节点。
必须通过 v-slot 取出底层组件实例,再用 渲染,才能被 KeepAlive 正常捕获缓存。
官方标准插槽写法(现在必须用)
<router-view v-slot="{ Component }"> <KeepAlive include="UserList,OrderPage"> <component :is="Component" /> </KeepAlive> </router-view>4. router
Vue Router 是 Vue.js 的官方路由管理器。
单页应用的特点:页面跳转不会重新加载整个 HTML 页面,仅局部更新视图;
作用:建立 URL 地址与页面组件之间的映射关系,根据当前 URL 渲染匹配的组件。
用法
- 安装vue-router
npm install vue-router@4 - 创建路由文件 src/router/index.ts
- 安装vue-router
import { createRouter, createWebHistory } from 'vue-router' import type { RouteRecordRaw } from 'vue-router' // 路由配置 const routes: RouteRecordRaw[] = [ {path: '/',redirect: '/home'}, {path: '/home',name: 'Home',component: () => import('@/views/Home.vue')}, {path: '/about',name: 'About',component: () => import('@/views/About.vue')} ] // 创建路由 const router = createRouter({ history: createWebHistory(), routes }) export default router- main.ts中引入和注册
import router from './router' app.use(router)就可以成功通过改变URL加载不同组件了
