vue3(四)(生命周期,父子组件传参,动态组件)
组件的生命周期
简单来说就是一个组件从创建 到 销毁的 过程 成为生命周期
在我们使用Vue3 组合式Api是没有 beforeCreate 和 created 这两个生命周期的,用setup函数代替,但setup又是在beforeCreate和created之前执行。
<template> <div> <input type="text" v-model="message"> </div> </template> <script setup lang="ts"> import { ref ,onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted} from 'vue' let message = ref<string>('hello') console.log('setup') onBeforeMount(()=>{ console.log('创建之前') //在组件DOM实际渲染安装之前调用。在这一步中,根元素还不存在。 }) onMounted(()=>{ console.log('创建完成') //在组件的第一次渲染后调用,该元素现在可用,允许直接DOM访问 }) onBeforeUpdate(()=>{ console.log('更新之前') //数据更新时调用,发生在虚拟 DOM 打补丁之前 }) onUpdated(()=>{ console.log('更新完成') //DOM更新后,updated的方法即会调用。 }) onBeforeUnmount(()=>{ console.log('销毁之前') //在卸载组件实例之前调用。在这个阶段,实例仍然是完全正常的。 }) onUnmounted(()=>{ console.log('销毁完成') //卸载组件实例后调用。调用此钩子时,组件实例的所有指令都被解除绑定,所有事件侦听器都被移除,所有子组件实例被卸载 }) </script> <style> </style>
setup函数在script标签上写是语法糖,还可以写成函数形式,但是要return出来
<template> <div class="about"> <h1>{{ msg }}</h1> </div> </template> <script> import {reactive} from 'vue' export default { setup() { let msg=reactive({name:'hello'}) return {msg} }, } </script>
父子组件传参
方案一
vue3中,新增了 defineComponent ,它并没有实现任何的逻辑,只是把接收的 Object 直接返回,它的存在是完全让传入的整个对象获得对应的类型,它的存在就是完全为了服务 TypeScript 而存在的
普通的组件就是一个普通的对象,既然是一个普通的对象,那自然就不会获得自动的提示,加上 defineComponent() 之后,就完全不一样了,可以获得自动提示,vue2、vue3的自动提示都有
父组件
<template> <div class="about"> <h1>{{ msg }}</h1> <HelloWorld title="我是标题"> </HelloWorld> </div> </template> <script> import HelloWorld from '../components/HelloWorld.vue' import {reactive,defineComponent} from 'vue' export default defineComponent({ name:"AboutView", components:{ HelloWorld }, setup() { let msg=reactive({name:'hello'}) return {msg} }, }) </script>
子组件
<template> <div class="hello"> <h1>{{ title }}</h1> <h3>这是HelloWorld组件</h3> </div> </template> <script lang="ts"> import { defineComponent } from 'vue'; export default defineComponent({ name: 'HelloWorld', props: { title: String, }, setup(props){ console.log(props.title) } }); </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped lang="less"> h3 { margin: 40px 0 0; } </style>
方案二
子组件通过defineProps接受传过来的值
如果defineProps报错,找到eslint.js
文件,在env
处添加代码 'vue/setup-compiler-macros': true,重启即可解决,如图
父组件
<template> <div> <input type="text" v-model="message"> <HelloWorld title='我是标题' :data="data"> </HelloWorld> </div> </template> <script setup lang="ts"> import HelloWorld from '../components/HelloWorld.vue' import { ref ,reactive} from 'vue' let message = ref<string>('hello') const data = reactive<number[]>([1, 2, 3]) </script> <style> </style>
子组件
<template> <div class="HelloWorld"> 子组件 <h1>{{ title }}</h1> <div>{{ data }}</div> </div> </template> <script setup lang="ts"> defineProps<{ title:string, data:number[] }>() </script>
如果你使用的不是TS
defineProps({ title:{ default:"", type:String }, data:Array })
TS 特有的默认值方式
withDefaults是个函数也是无须引入开箱即用接受一个props函数第二个参数是一个对象设置默认值
type Props = { title?: string, data?: number[] } withDefaults(defineProps<Props>(), { title: "张三", data: () => [1, 2, 3] })
子组件给父组件传参
是通过defineEmits派发一个事件
子组件
<template> <div class="HelloWorld"> <button @click="clickTap">派发给父组件</button> </div> </template> <script setup lang="ts"> import { reactive } from 'vue' const list = reactive<number[]>([4, 5, 6]) const emit = defineEmits(['on-click']) const clickTap = () => { emit('on-click', list) } </script>
父组件
<template> <div> <HelloWorld @on-click="getList"> </HelloWorld> </div> </template> <script setup lang="ts"> import HelloWorld from '../components/HelloWorld.vue' import { reactive } from 'vue'; const data = reactive<number[]>([1, 2, 3]) const getList = (list: number[]) => { console.log(list,'父组件接受子组件'); } </script> <style> </style>
子组件暴露给父组件内部属性
通过defineExpose
我们从父组件获取子组件实例通过ref
子组件
<template> <div class="HelloWorld"> <button @click="clickTap">派发给父组件</button> </div> </template> <script setup lang="ts"> import { reactive } from 'vue' const list = reactive<number[]>([4, 5, 6]) const emit = defineEmits(['on-click']) const clickTap = () => { emit('on-click', list) } //子组件暴露给父组件内部属性 defineExpose({ list }) </script>
父组件
<template> <div> <HelloWorld ref="helloRef" @on-click="getList"> </HelloWorld> </div> </template> <script setup lang="ts"> import HelloWorld from '../components/HelloWorld.vue' import { reactive,ref } from 'vue'; const helloRef = ref(null) const data = reactive<number[]>([1, 2, 3]) const getList = (list: number[]) => { console.log(list,'父组件接受子组件'); console.log(helloRef.value) } </script> <style> </style>
动态组件
什么是动态组件 就是:让多个组件使用同一个挂载点,并动态切换,这就是动态组件。
在挂载点使用component标签,然后使用v-bind:is=”组件”
使用场景
tab切换 居多
注意事项
1.在Vue2 的时候is 是通过组件名称切换的 在Vue3 setup 是通过组件实例切换的
2.如果你把组件实例放到Reactive Vue会给你一个警告runtime-core.esm-bundler.js:38 [Vue warn]: Vue received a Component which was made a reactive object. This can lead to unnecessary performance overhead, and should be avoided by marking the component with `markRaw` or using `shallowRef` instead of `ref`.
Component that was made reactive:
这是因为reactive 会进行proxy 代理 而我们组件代理之后毫无用处 节省性能开销 推荐我们使用shallowRef 或者 markRaw 跳过proxy 代理
修改如下
<template> <div class="about"> <button v-for="item in tab" :key="item.name" @click="onchange(item)"> {{ item.name }} </button> <component :is="current.comName"></component> </div> </template> <script setup lang="ts"> import { reactive, markRaw } from "vue"; import ComA from "../components/ComA.vue"; import ComB from "../components/ComB.vue"; type Tabs = { name: string; comName: any; }; const tab = reactive<Tabs[]>([ { name: "A组件", comName: markRaw(ComA), }, { name: "B组件", comName: markRaw(ComB), }, ]); let current = reactive({ comName: tab[0].comName, }); const onchange = (val) => { current.comName = val.comName; }; </script>