目录
一、概述
- 指导思想:小组件组成大组件,大组件组成组件集,即页面
- 组件树:最后页面会构成一个组件树,根组件是App.vue,各个页面也为组件,注册在根组件下,并类似向下扩展
二、组合API函数
2.1 路径配置
- style中调用
- 引用外部样式文件:
@import '~@/assets/css/base.css';
- 注意:句尾有分号,引用有波浪线
- 引用外部样式文件:
- script中调用
- 写法:
import Home from '@/views/Home/Home.vue'
- 注意:句尾无分号,引用无波浪线
- 写法:
- setup语法糖:
- 写法:
<script lang="ts" setup>
- 功能:默认将script标签里的所有函数和变量都导出给模板用,组件导入无需注册即可使用
- 写法:
2.2 生命周期函数
-
生命周期钩子
选项式 API beforeCreate setup内默认 created setup内默认 beforeMount onBeforeMount mounted onMounted beforeUpdate onBeforeUpdate updated onUpdated beforeUnmount onBeforeUnmount unmounted onUnmounted errorCaptured onErrorCaptured renderTracked onRenderTracked renderTriggered onRenderTriggered activated onActivated deactivated onDeactivated -
Home.vue演示代码(常规)
<template> <button @click="test">{{ title }}</button> </template> <script lang="ts"> import { defineComponent, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted, ref, } from "vue"; export default defineComponent({ name: "home", setup() { const title = ref("标题"); const test = () => { title.value = "更新"; }; onBeforeMount(() => { console.log("onBeforeMount执行"); }); onMounted(() => { console.log("onMounted执行"); }); onBeforeUpdate(() => { // 数据更新才会执行 console.log("onBeforeUpdate执行"); }); onUpdated(() => { // 数据更新才会执行 console.log("onUpdated执行"); }); onBeforeUnmount(() => { console.log("onBeforeUnmount执行"); }); onUnmounted(() => { console.log("onUnmounted执行"); }); return { test, title }; }, }); </script>
-
setup语法糖写法
<template> <button @click="test">{{ title }}</button> </template> <script lang="ts" setup> // 注意上面标签里加了setup,下面无return和export import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted, ref, } from "vue"; const title = ref("标题"); const test = () => { title.value = "更新"; }; onBeforeMount(() => { console.log("onBeforeMount执行"); }); onMounted(() => { console.log("onMounted执行"); }); onBeforeUpdate(() => { console.log("onBeforeUpdate执行"); }); onUpdated(() => { console.log("onUpdated执行"); }); onBeforeUnmount(() => { console.log("onBeforeUnmount执行"); }); onUnmounted(() => { console.log("onUnmounted执行"); }); </script>
-
效果图
-
具体调用时机
2.3 监听函数
- 功能:监控变量值,若变化,则做相应处理
2.3.1 watch函数
Home.vue演示代码
- 基本数据类型
<template> <h1>{{ age }}</h1> <button @click="add">增加年龄</button> <button @click="stop">停止监控</button> </template> /****************监控ref对象(常规写法)***********************/ <script lang="ts"> import { defineComponent, ref, watch } from "vue"; export default defineComponent({ name: "home", setup() { const age = ref(18); const add = () => age.value++; // watch传入:监控项、回调函数 const stop = watch(age, (cur, pre) => { console.log("旧值:" + pre); console.log("现值:" + cur); }); return { age, add, stop }; }, }); </script> /****************监控ref对象(setup语法糖写法)***********************/ <script lang="ts" setup> import { ref, watch } from "vue"; const age = ref(18); const add = () => age.value++; const stop = watch(age, (cur, pre) => { console.log("旧值:" + pre); console.log("现值:" + cur); }); </script> /****************监控多个对象(setup语法糖写法)******************/ <script lang="ts" setup> import { ref, watch } from "vue"; const Name1 = ref("preDuke"); const Name2 = ref("preLuck"); // 此处newValues是一个数组 watch([Name1, Name2], (newValues, prevValues) => { console.log(newValues, prevValues); }); // 终端输出: ["Duke", "preLuck"] ["preDuke", "preLuck"] Name1.value = "Duke"; // 终端输出: ["Duke", "Luck"] ["Duke", "preLuck"] Name2.value = "Luck"; </script>
- 复杂数据类型
<template> <h1>{{ person.age }}</h1> <button @click="add">增加年龄</button> <button @click="stop">停止监控</button> </template> /****************精确监控reactive对象子项(setup语法糖)***********************/ <script lang="ts" setup> import { reactive, watch } from "vue"; const person = reactive({ name: "duke", age: 18 }); const add = () => person.age++; const stop = watch( // 监控项:复杂数据类型子项 () => person.age, // 回调函数:新、旧值,也可不传()=>{} (cur, pre) => { console.log("旧值:" + pre); console.log("现值:" + cur); } ); </script> /****************监控reactive对象:仅写watch函数******************/ const stop = watch( // 监控对象:reactive () => person, // 回调函数:只能获取最新值,旧值无法获取 (cur) => { console.log("现值:" + cur.age); }, // 可选参数:开启监控嵌套子项、首次加载页面便执行一次(默认不执行) // { deep: true, immediate: true } { deep: true } );
- 效果
2.3.2 watchEffect函数
- Home.vue演示代码
<template></template> <script lang="ts" setup> import { watchEffect, ref, reactive } from "vue"; const person = reactive({ name: "duke", age: 18, }); // watchEffect函数特点: // 1、自动检索回调函数中的ref、reactive变量进行跟踪 // 2、页面首次加载即执行一次回调函数 // 名字不改变,终端不输出任何信息 const namewatch = watchEffect(() => console.log(person.name)); // 每秒都会执行一次 // const agewatch = watchEffect(() => console.log(person.age)); // 函数功能:每隔一秒,年龄增加1 setInterval(() => { person.age++; }, 1000); </script>
三、父子组件通信(以setup语法糖写法)
3.1 基础组件
以下为父子组件的格式
- 父组件:Home.vue
<template> <h1>这是父组件</h1> <Child></Child> </template> <script lang="ts" setup> // 这里导入组件 import Child from "@/views/Child.vue"; </script>
- 子组件:Chile.vue
<template> <h2>这是子组件</h2> </template> <script lang="ts" setup> </script>
3.1.1 保持组件激活状态
- 代码示例
<!-- --> <keep-alive> <!-- 子组件 --> <Child></Child > </keep-alive>
- 功能:只要不刷新页面,切换页面,原页面的输入和选择都会保持原样(放在缓存里)
- 应用场景:当组件内包含交互式操作时,保留页面当前操作有助于提高用户体验
3.1.2 动态选择展示组件
- 父组件@/src/Home.vue
<template> <h1>这是父组件</h1> <!-- 随着num的值变化,会动态选择展示Child组件或者Child1组件 --> <component :is="num % 2 == 0 ? Child : Child1"></component> </template> <script lang="ts" setup> import { ref } from "vue"; // 这里导入组件 import Child from "@/views/Child.vue"; import Child1 from "@/views/Child1.vue"; let num = ref(0); // 设置了一个定时器,每一秒num值增加1 setInterval(() => num.value++, 1000); </script>
- 子组件
// 文件@/views/Child.vue <template> <h2>展示组件A</h2> </template> // 文件@/views/Child1.vue <template> <h2>组件B</h2> </template>
3.2 父传子数据
3.2.1 基本写法
单向传值:即子组件中改变值的话,不会影响到父组件值,父组件值变动,会实时改变子组件数据
-
Home.vue
<template> <h1>这是父组件</h1> <!-- 第一种:v-bind赋值(非字符串都是这个,包括数字、布尔)--> <Child :msg="test1"></Child> <!-- 第二种:字符串赋值(非字符串会被强制转换)--> <Child msg="第二种"></Child> <!-- 第三种:表达式赋值--> <Child :msg="test3.name + '的ID是' + test3.id"></Child> <!-- 将对象传给msg(第一种:传对象)--> <Child :msg="test3"></Child> </template> <script lang="ts" setup> import { ref, reactive, defineComponent } from "vue"; import Child from "@/views/Child.vue"; let test1 = ref("第一种"); let test3 = reactive({ id: 1, name: "第三种", }); </script>
-
Child.vue
<template> <div style="border: 3px solid black"> <h2>这是子组件</h2> <h3>接收到的字符串:{{ msg }}</h3> </div> </template> <script lang="ts" setup> // 见3.2.2节详解,每个组件只能有一个defineProps defineProps({ msg: [String, Object], }); </script>
- router-view标签:router-view也可以传参,可以把其看成vue中一个默认组件,
<router-view msg="hello"></router-view>
- :msg=***理解:
- 等号左边:父组件传给子组件的变量名,父组件不定义,子组件做接收校验等操作
- 等号右边:拼接的字符串或者父组件中的变量,父组件需要定义
- router-view标签:router-view也可以传参,可以把其看成vue中一个默认组件,
-
效果
3.2.2 props属性校验
- 验证写法
- 类型限定:如顺序1中写法,其他类型有
String,Number,Boolean,Array,Object,Function,Promise
- 详细验证:
以下msg-*均为父组件传进来的变量名,大括号中均为验证条件// 多个可能的类型 msg-A: [String, Number], // 此项必须传值,否则控制台warn msg-B: { type: String, required: true }, // 带有默认值,即父组件无:msg=***语句时,这个语句才被激活 msg-C: { type: Number, default: 100 }, // 带默认值:默认值从一个函数获取,返回一个对象(类似Python的字典) msg-D: { type: Object, default: () => { return { message: 'hello' } } }, msg-E: { // 自定义验证函数,这个值必须匹配下列字符串中的一个 t-fun: (value) => { // 当验证失败的时候,(开发环境构建版本的)Vue将会产生一个控制台的警告 return ['success', 'warning', 'danger'].indexOf(value) !== -1 }
- 类型限定:如顺序1中写法,其他类型有
3.2.3 ref、reactive、toref、toRefs浅析
- 响应式实现机制
proxy对象:接收两个参数(Handler,Target)前者实现监控数据功能,后者存储数据
- 函数解释
api函数 解释 ref() const ex_ref = ref(a);
,语句表示ref()函数接收传入的数据a,并返回一个RefImpl对象(其value属性为a的值),RefImpl对象与a无关联,即ex_ref的value值变化,a不变化,反之亦然reactive() const ex_reactive = reactive(b);
,语句表示reactive()函数接收传入的数据b,并返回一个Proxy对象(其Target属性为b的值),Proxy对象与b无关联,即ex_reactive的值变化,b不变化,反之亦然toRef() 较少用, const a = toRef(b,"title");
,语句表示将reactive对象b的子项title包装成RefImpl对象并赋值a,修改a的值响应式修改b.title的值,常用于向外部定义的函数传参,函数修改a的值的时候会同步到b中toRefs() toRefs(b);
,语句表示将reactive对象b的所有子项均转换为RefImpl对象,b.子项名
均为RefImpl对象,常用于解构reactive对象,如下示例toRefs示例 <template> <h1>数据:{{ title }}</h1> </template> <script lang="ts" setup> import { reactive, ref, toRefs } from "vue"; const a = reactive({ title: "duke", age: 18 }); // 第一种用法:解构a,name和age均为RefImpl对象 const { title, age } = toRefs(a); console.log(title.value); // console.log(title.value); //这种解构可以直接拿任意属性 // const { age } = toRefs(a); </script>
3.3 子传父事件
事件传送:通常子组件向父组件传送的是事件,父组件需要知道子组件发生了什么事件,并做相应的反应
- 父组件
<template> <h1>这是父组件</h1> <!-- 父组件分别监听change和changearg事件,并配备响应函数 --> <Child @change="log" @changearg="logarg"></Child> </template> <script lang="ts" setup> import Child from "@/views/Child.vue"; const log = () => console.log("捕获到子组件事件change"); // 获取子组件带参数事件的参数方法:限定参数类型 const logarg = (arg: string) => console.log("捕获到子组件事件changearg,参数:" + arg); </script>
- 子组件
<template> <button @click="send">发送事件</button> <button @click="sendarg">发送带参数的事件</button> </template> <script lang="ts" setup> // 每个组件只能有一个defineEmits let emit = defineEmits(["change", "changearg"]); const send = () => emit("change"); const sendarg = () => emit("changearg", 123); </script>
3.4 父获取子变量、方法
props的逆向
-
父组件
<template> <h1>这是父组件</h1> <!-- 此处son是一个变量 --> <Child ref="son"></Child> </template> <script lang="ts" setup> import { ref } from "@vue/reactivity"; import Child from "@/views/Child.vue"; import { onMounted } from "@vue/runtime-core"; // 变量son赋初值 const son = ref(null); // 只能在挂在后才有数据 onMounted(() => { // 拿到子组件的数据 console.log(son.value?.msg); // 拿到子组件的方法 son.value?.test(); }); </script>
-
子组件
<template> <h1>这是子组件</h1> </template> <script lang="ts" setup> import { ref } from "vue"; const msg = ref("good"); const test = () => { console.log("这是子组件方法"); }; defineExpose({ msg, test }); </script>
四、插槽类子组件
- 功能:针对布局(css)相同,内容(html)不同的结构,子组件留坑(占位符),父组件来填(传入数据)
- 示例:
4.1 插槽子组件
- 基本规则
- 默认值:父组件不传,即显示默认值
- 优先级:父组件中传给具名插槽的,直接对号入座,传给不具名插槽,按顺序送入子组件不具名插槽
- 子组件写法
<template> <!-- 此盒子是为了集中设置作为插槽子组件的样式--> <div class='childStyle'> <!-- 此标签在调用组件时会固定显示 --> <h1>h1插槽子组件</h1> <!-- 不具名插槽若有传入值,则会统一一样,无传入值,则默认值,看效果 --> <!-- 不具名插槽写法一:无默认值,不传即不显示 --> <slot></slot> <!-- 不具名插槽写法二:有默认值,不传即显示默认值 --> <slot><h3>h3不具名插槽默认值</h3></slot> <!-- 具名插槽:有默认值,不传即显示默认值 --> <slot name="no_1"><h4>h4具名插槽默认值</h4></slot> <!-- 具名插槽:无默认值,不传即不显示 --> <slot name="no_2"></slot> </div> </template>
4.2 插槽父组件
4.2.1 啥都不传
- 父组件
<template> <Son></Son> </template> <script lang="ts" setup> import Son from '../components/son.vue' </script>
- 显示
4.2.2 全都传
- 父组件
<template> <Son> <!-- 插槽的传值必须被template标签包裹 --> <template v-slot:no_1> <h1>父组件传给具名插槽no_1</h1> </template> <template v-slot:no_2> <h1>父组件传给具名插槽no_2</h1> </template> <template> <h1>父组件传给不具名插槽</h1> </template> </Son> </template> <script lang="ts" setup> import Son from '../components/son.vue' </script>
- 显示
不具名插槽:如果父组件不传值,则各自使用各自的默认值;若传值,则所有不具名插槽将会格式化为一样的,如显示第2、3行
向不具名插槽传多个值:会被集合起来作为一个不具名插槽发送值传给子组件
4.3 作用域插槽
- 功能:父组件需要获取插槽子组件的数据,以具名插槽和不具名插槽为基础
- 子组件
<template> <div class="childStyle"> <!-- 不具名插槽创建插槽属性def1,将变量def值与其绑定 --> <slot :def1="def">不具名插槽不传值则显示此信息</slot> <!-- 具名插槽创建插槽属性no1,将变量first值与其绑定 --> <slot name="no_1" :no1="first">具名插槽no_1不传值则显示此信息</slot> <!-- 具名插槽创建插槽属性no2,no3,将变量second、third值分别与其绑定 --> <slot name="no_2" :no2="second" :no3="third"> 具名插槽no_2不传值则显示此信息 </slot> </div> </template> <script lang="ts" setup> import { ref } from 'vue'; // 子组件的值 const def = ref('子组件默认插槽绑定的属性值def'); const first = ref('子组件具名插槽值no_1绑定的属性值first'); const second = ref('子组件具名插槽no_2绑定的属性值second'); const third = ref('子组件具名插槽no_2绑定的属性值third'); </script>
- 父组件
<template> <Son> <!-- 给默认插槽起个别名defslot,拿其中的属性def1 --> <!-- 简写:#default="defslot" --> <template v-slot="defslot"> <h5>{{ defslot.def1 }}</h5> </template> <!-- 选择具名插槽no_1起个别名obj1,拿其中的属性no1 --> <!-- 简写:#no_1="obj1" --> <template v-slot:no_1="obj1"> <h5>{{ obj1.no1 }}</h5> </template> <!-- 选择具名插槽no_2,直接解构其中的属性no2、no3(ES6语法) --> <!-- 简写:#no_2="{ no2, no3 }" --> <template v-slot:no_2="{ no2, no3 }"> <h5>{{ no2 }}</h5> <h5>{{ no3 }}</h5> </template> </Son> </template>
传值:父传子在子组件首标签写,子传父在template标签写
- 效果