Vue3.0组合式API(四)-----组件化开发

一、概述

  • 指导思想:小组件组成大组件,大组件组成组件集,即页面
  • 组件树:最后页面会构成一个组件树,根组件是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
    beforeCreatesetup内默认
    createdsetup内默认
    beforeMountonBeforeMount
    mountedonMounted
    beforeUpdateonBeforeUpdate
    updatedonUpdated
    beforeUnmountonBeforeUnmount
    unmountedonUnmounted
    errorCapturedonErrorCaptured
    renderTrackedonRenderTracked
    renderTriggeredonRenderTriggered
    activatedonActivated
    deactivatedonDeactivated
  • 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=***理解:
      • 等号左边:父组件传给子组件的变量名,父组件不定义,子组件做接收校验等操作
      • 等号右边:拼接的字符串或者父组件中的变量,父组件需要定义
  • 效果
    在这里插入图片描述

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
      }
      

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标签写

  • 效果
    在这里插入图片描述

跳转至vue总篇目录

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值