【VUE3】保姆级基础讲解(三)非父子组件通讯,$refs,动态组件,keep-alive,Composition API

news/2024/12/2 20:44:38/

目录

非父子组件通讯

全局事件总线mitt库

组件的生命周期

 $refs

动态组件

keep-alive

 异步打包

v-model绑定组件

Composition API

定义响应式数据

readonly

toRefs与toRef

computed

$ref

生命周期钩子

provide和inject

watch侦听

watchEffect

script setup语法

defineProps和defineEmits


非父子组件通讯

 即在上层组件中使用 provide ,在下层组件中使用 inject

上层组件:

<script>
import infos from './components/infos.vue'
export default{components: { infos },provide:{name:'hhh',age:18}
}
</script>

如果传递的是自身的变量,应该将provide函数处理为函数,原因是使得this能指向组件

<script>
import infos from './components/infos.vue'
export default{components: { infos },data(){return{name:'hhh'}},provide(){return{name:this.name}}
}
</script>

 如果我们要传递的值可能会发生改变,那么需要 computed 方法进行响应式跟踪

使用这种方法,下层组件在使用时  应该加  .value

<template>
<div><infos></infos><button @click="name='qwee'"></button>
</div>
</template>
<script>
import infos from './components/infos.vue'
import { computed} from 'vue'
export default{components: { infos },data(){return{name:'hhh'}},provide(){return{name:computed(()=>{return this.name})}}
}
</script>
<style scoped>
</style>

下层组件


<template>
<h1>我是第二个子组件{{name}}</h1>
</template><script>
export default{inject:['name']
}
</script>
<style scoped>
</style>

全局事件总线mitt库

安装管理事件总线管理工具

npm i hy-event-store

一般来说会创建一个js文件创建一个新的事件总线对象

import{HYEventBus}from 'hy-event-store'
const eventbus = new HYEventBus()
export default eventbus

对于需要发送数据的组件,采用eventbus.emit方法,里面的参数,第一个是传递的名字,后面是内容


<template>
<div><h1>我是第二个子组件</h1><button @click='transfer'>111</button>
</div>
</template>
<script>
import eventbus from '@/utils/hy_bus.js'
export default{methods:{transfer(){console.log(111);eventbus.emit("event1",'boke',18)}}
}
</script>
<style scoped>
</style>

需要监听的组件,采用 event.on接受,并在里面采用回调函数得到参数并做出响应

<script>
import infos from "./components/infos.vue";
import eventbus from "./utils/hy_bus.js";
export default {components: { infos },data() {return {name: "hhh1",};},created() {eventbus.on("event1", (name,age) => {console.log(name);this.name = name;});},
};
</script>

但是一般需要监听的组件的写法是:

export default {// components: { infos },methods:{eventFn(name,age){console.log(name);console.log(age);}},created() {eventbus.on("event1", this.eventFn);},unmounted(){eventbus.off('event1',this.eventFn)}
};
</script>

给回调函数定义函数名,并在此组件销毁时移除这个事件监听
 

组件的生命周期

 也就是组件从创建到销毁会经历一系列的周期,在每个生命阶段都可以指定调用回调函数,生命周期与回调函数关系如下图

<script>
import infos from "./components/infos.vue";
import eventbus from "./utils/hy_bus.js";
export default {components: { infos },methods:{eventFn(name,age){console.log(name);console.log(age);}},beforeCreate(){console.log('创建组件实例之前');},created(){console.log('已经创建组件实例');console.log('1.发送网络请求,请求数据');console.log('2.监听eventbus事件');console.log('3.监听watch数据');},beforeMount(){console.log('组件挂载之前');},mounted(){console.log('已经挂载,虚拟dom >> 真实dom');console.log('获取DOM,使用dom');},beforeUpdate(){console.log('数据改变了,但是页面还没改变');},updated(){console.log('页面发生了改变');},beforeUnmount(){console.log('准备卸载VNode');},unmout(){console.log('组件已经卸载完成');}
}</script>

 $refs

在vue中一般不会直接使用document.querySelector得到DOM并操作它

但是如果非要和DOM产生关联,VUE提供了一种方法:给元素绑定一个ref属性

使用方法

在元素中绑定  ref='xxx'

js调用中使用 this.$ref.xxx


<template>
<div><button @click='transfer'>111</button><span ref='span'></span>
</div>
</template>
<script>
import eventbus from '@/utils/hy_bus.js'
export default{methods:{transfer(){console.log(this.$refs.span);}}
}
</script>
<style scoped>
</style>

当然这个方法也可以获取子组件实例,在获取之后甚至可以操作子组件内部的方法

this.$refs.item.fn()

如果想获取子组件中的根元素

this.$refs.item.$el

动态组件

当面对这个需求:点击按钮切换界面

对于切换界面,我们会引入n个vue文件,然后在APP.vue中使用v-if来进行选择

但是这样是很繁琐的,这里可以采用动态组件

<component is="XXXX"></component>

在引入组件之后,使用is="XXX"来选择显示XXX组件

keep-alive

对于上述动态组件的例子,当我切换界面时,原有界面会被销毁

对于需要频繁切换的界面,这样的效率是及其低下的,且如果在原有界面中有需要保持的数据,例如有一个计数器,那么在切换界面后我们还是希望保存这个数据,既将原有界面放置在缓存中

可以使用keep-alive

只需要使用  <keep-alive>  装载想要缓存的界面

  <keep-alive><component :is="products[currentIndex]"></component></keep-alive>

以include举例,我只想要部分组件是keep-alive的,那么:

  <keep-alive include="page"><component :is="products[currentIndex]"></component></keep-alive>

 但是这个地方的 page  也就是组件名称,需要在组件内部自己定义

<script>
export default {name:'page',data(){return{value:0}},

 异步打包

在我们运行 npm run  build 进行打包时,会生成两个js文件

第一个js文件包含自己编写的所有JS文件的总和,第二个js文件包含的是第三方库的js代码

很显然,如果我们自己编写了很多组件,且某些组件很大时,将其全部放在一个js文件的方式是不妥的,因为当我需要向服务器申请页面时,需要将全部代码都进行下载

那么在引入组件时,可以采用异步申请的方式,也就是在打包时,会给这个组件单独创造一个js文件

用到了defineAsyncComponent函数

import{ defineAsyncComponent } from 'vue'const AsyncNavitem = defineAsyncComponent(()=>import('./components/navitem.vue'))

其他组件方法和普通的一样,这时打包会创建三个js文件

 

 

v-model绑定组件

v-model的普通用法参照: 

 【VUE3】保姆级基础讲解(一):初体验与指令_独憩的博客-CSDN博客

也可以将变量绑定到组件中

<template><page v-model="value"></page>
</template>
<script>
import Page from './components/page.vue'
export default {data(){return{value:0}},components:{Page}, 
}
</script>
<style scoped>
</style>

这个地方v-model其实相当于:

  <page :modelValue="value" @updata:modelValue="value=$event"></page>

既将父组件的value传给子组件的modelValue(固定名字)中,并绑定updata:modelValue方法,将value值改变为子组件传过来的数值

 在子组件中,应该接受参数  modelValue ,并传递数值方法名称updata:modelValue

<script>
export default {props:{modelValue:{type:Number,default:0}},emits:['update:modelValue'],methods:{add(){this.$emit('update:modelValue',100)}} 
}
</script>

当然如果不想要 modelValue这个名字,也可以自定义,下面将其改为了myname,那么在子组件中也需要修改接收变量名称,以及传递数值方法名称:update:myname

<page v-model:myname="value"></page>

Composition API

 

举一个按钮计数器的例子来感受基础用法

<template><h1>{{Value}}</h1><button @click="increment">+</button><button @click="decrement">-</button>
</template>
<script>import { ref } from 'vue'export default {setup(){let Value = ref(0)//响应式数据let increment = ()=>{Value.value++}let decrement = ()=>{Value.value--}return{Value,increment,decrement}}}
</script>
<style scoped>
</style>

将所有逻辑都写在setup函数中,并返回需要调用的变量和方法

定义响应式数据

在setup函数中定义响应式数据,需要借助别的工具

对于简单数据类型,使用ref函数

对于复杂数据类型,使用reactive函数

    setup(){let Value = ref(0)let diff = reactive({name:'kobe',age : 18})return{Value,diff}}

 对于复杂数据类型,在使用时需要:diff.name  diff.age

对于ref函数,在内部逻辑中若想改变或获取其值,应该加.value,例如 Value.value,但是在模板中使用则不需要

 当然,ref函数也是可以定义复杂数据类型的,在逻辑中操作时也需要加.value:

<template><h1>{{Value.counter}}</h1><button @click="increment">+</button><button @click="decrement">-</button>
</template>
<script>import { ref,reactive } from 'vue'export default {setup(){let Value = ref({counter:0})let increment = ()=>{Value.value.counter++}let decrement = ()=>{Value.value.counter--}return{Value,increment,decrement}}}
</script>
<style scoped>
</style>

readonly

一般父组件给子组件传递数据时,不希望子组件对数据进行更改

你们可以使用readonly函数,让数据是只读的

<template><h1>{{Value.counter}}</h1><page :info="ValueReadonly"></page>
</template>
<script>import { ref,reactive,readonly } from 'vue'import page from './components/page.vue'export default {components:{page},setup(){let Value = ref({counter:0,name:'kobe'})let ValueReadonly =readonly(Value) return{Value,ValueReadonly}}}
</script>
<style scoped>
</style>

toRefs与toRef

在解构时保持数据的响应式

  export default {components:{page},setup(){let Value = reactive({counter:0,name:'kobe'})let{counter,name} = toRefs(Value)let name1 = toRef(Value,'name')return{counter,name,name1}}}

computed

使用时直接在 computed()定义函数,默认调用其getter

<template><h1>{{fullname}}</h1>
</template>
<script>import { ref,reactive,readonly,computed } from 'vue'// import page from './components/page.vue'export default {components:{page},setup(){let name = reactive({firstname :'lee',lastname :'xl'})let fullname = computed(()=>{return name.firstname+''+name.lastname})return{name,fullname}}}
</script>
<style scoped>
</style>

$ref

在之前的options api中若想得到dom元素,可以使用ref属性,在js调用中采用 this.$ref.XXX的方法

但是在setup函数中不存在this ,那么可以:

定义一个空的ref,名字与DOM中定义的名字一致

<template><h1 ref="h11">123</h1><button @click="changeh11"></button>
</template>
<script>import { ref,reactive,readonly,computed,onMounted} from 'vue'import page from './components/page.vue'export default {components:{page},setup(){let h11 = ref()let changeh11 =function(){h11.value.innerHTML=222}return{h11,changeh11}}}
</script>
<style scoped>
</style>

生命周期钩子

与options API类似,Composition API也有生命周期函数,只是其一般是 onX函数

<script>import {onMounted} from 'vue'export default {components:{page},setup(){onMounted(()=>{console.log(111);})return{}}}
</script>

与options API对应关系为

如果需要实现beforeCreate和created功能,直接在setup函数中编写就可以了 

 

provide和inject

 父组件

<script>import { ref,reactive,computed,provide} from 'vue'import page from './components/page.vue'export default {components:{page},setup(){let name = ref('kobe')provide('name',name)return{name}}}
</script>

子组件

<script>import { ref,inject } from 'vue'export default {setup(){let name1 = inject('name','hhhh')//默认值为hhhhreturn{name1}}}
</script>

 

watch侦听

与options api类似

<script>import { ref,reactive,watch} from 'vue'export default {setup(){let name = ref('kobe')watch(name,(newvalue,oldvalue)=>{console.log(newvalue);console.log(oldvalue);})return{name}}}
</script>

也可以选择immediate和deep参数

      watch(name,(newvalue,oldvalue)=>{console.log(newvalue);console.log(oldvalue);},{immediate:true,deep:true})

 

watchEffect

当侦听到某个数据发生变化时,我们希望进行某种操作,就用watchEffect

1、watchEffect传入的函数默认会直接执行

2、在执行过程中,会自动收集依赖

<script>import { ref,reactive,watchEffect} from 'vue'export default {setup(){let counter =ref(0)watchEffect(()=>{console.log(counter.value);})return{counter}}}
</script>

 每次counter发生改变,watchEffect都会自动收集依赖并执行

若希望在某种情况下停止监听:

      let stopwatch = watchEffect(()=>{console.log(counter.value);if(counter.value>=10){stopwatch()}})

script setup语法

可以简写setup函数,不再需要写setup函数,也不需要写return

直接使用script setup

<script setup>import { ref, reactive, watchEffect } from 'vue'import page from './components/page.vue'import Counter from './js/counter.js'let counter =ref(0)function higher(){counter.value++}function lower(){counter.value--}
</script>

 

defineProps和defineEmits

用于在语法糖下接受和发送数据与事件

<template><h1>{{props.name}}</h1><button @click="btnclick">+</button></template>
<script setup>import { ref,watch } from 'vue'import Counter from '../js/counter.js'//接受数据let props = defineProps({name:{type:String,default:'lee'}})//发出事件let emits = defineEmits(['btnclick'])function btnclick(){emits('btnclick','aaaa')}
</script>


http://www.ppmy.cn/news/6713.html

相关文章

[JavaEE] 线程与进程的区别详解

专栏简介: JavaEE从入门到进阶 题目来源: leetcode,牛客,剑指offer. 创作目标: 记录学习JavaEE学习历程 希望在提升自己的同时,帮助他人,,与大家一起共同进步,互相成长. 学历代表过去,能力代表现在,学习能力代表未来! 目录 认识线程(Thread) 1. 线程是什么? 2. 为什么要有…

C#项目实战——【实例】企业人事管理系统(一):1、系统分析;2、系统设计;3、系统运行环境;

学习《C#从入门到精通》&#xff0c;边学边练记录实现过程。 1、系统分析 1.1、需求分析 基于其他企业人事管理软件的不足&#xff0c;要求能够制作一个可以方便、快捷地对职工信息进行添加、修改、删除的操作&#xff0c;并且可以在数据库中存储相应职工的照片。为了能够更…

【C++初阶】list的模拟实现

文章目录list的介绍list的模拟实现成员变量Member functionsconstructordestructoroperatorIterators正向迭代器反向迭代器beginendrbeginrendModifierspush_frontpop_frontpush_backpop_backinserteraseclear完整版代码list.hreverse_iterator.htest.cpplist的介绍 list是STL…

力扣:两数之和

原题链接&#xff1a;https://leetcode.cn/problems/two-sum/description/ 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是&…

numpy数组,numpy索引,numpy中nan和常用方法

一&#xff1a;【numpy数组】 1.1为什么要学习numpy 1.快速 2.方便 3.科学计算的基础库 1.2什么是numpy 一个python中做科学计算的基础库&#xff0c;重在数值计算&#xff0c;也是大部分python科学计算库的基础库&#xff0c;多用于在大型&#xff0c;多维数组上执行数组运…

如何定制化Spring Boot Starter,这次我终于学会了

文章目录什么是Spring Boot Starter实现步骤启动器自动配置包总结自定义Starter的实现逻辑Spring Boot Starter官网描述&#xff1a;Spring Boot Starter官方介绍 什么是Spring Boot Starter Starters可以理解为启动器&#xff0c;它包含了一系列可以集成到应用里面的依赖包&…

shell脚本四剑客之awk详解

文章目录awk的介绍awk能够干什么awk的格式工作原理&#xff1a;记录和域内建变量的用法1. FS2. OFS3.RS4. ORS5. NF6. NRBEGIN 和END语句块常见案例1. 使用NR行号提取ip2. 打印UID小于10的账号名称和UID信息3. 数学运算4. AWK打印硬盘设备名称&#xff0c;默认以空格为分割&…

买不到的数目(蓝桥杯C/C++A组真题详解)

题目详细&#xff1a; 题目思路&#xff1a; 对于这个题有一个定理 如果 a,b 均是正整数且互质&#xff0c;那么由 axby&#xff0c;x≥0&#xff0c;y≥0 不能凑出的最大数是 &#xff1a; a*b-a-b 具体的证明过程这里就不赘述 感兴趣的同学可以自行查找 这里就提供一种思…