1 组件间传参
**父组件-->子组件:**defineProps
**子组件-->父组件:**defineEmits(即自定义事件)
1.1 示例
- MyComponentView.vue
<template>
包含以下知识点:
<ul>
<li>【组件间传参】父组件-->子组件:通过defineProps</li>
<li>【组件间传参】子组件-->父组件:通过defineEmits(自定义事件)</li>
<li>
透传Attributes:传递给一个组件,却没有被该组件声明为 props 或 emits 的 attribute 或者 v-on
事件监听器(如下示例中的style属性)
</li>
<li>子父组件都声明同一事件(如click),那么都会被触发</li>
<li>使用透传属性:const attrs = useAttrs()</li>
</ul>
<MyComponent
title="button"
@click="onClick"
@CUSTOM_EVENT_A="onCustomEventA"
style="color: #ffffff"
/>
</template>
<script lang="ts" setup>
import MyComponent from './components/MyComponent.vue';
function onCustomEventA(event: MouseEvent, val: number) {
console.log('[parent] begin handle custom event');
const btn = event.target as HTMLButtonElement;
btn.innerText = val + '';
console.log('[parent] end handle custom event');
}
function onClick() {
console.log('[parent] onClick');
}
</script>
- MyComponent.vue
<template>
<button :title="title" @click="triggerCustomEvent($event)">clicked me</button>
</template>
<script lang="ts" setup>
import { ref, type PropType, useAttrs } from 'vue';
const count = ref(0);
const props = defineProps({
title: {
type: String as PropType<'button' | 'submit' | 'reset'>,
required: true,
default: 'submit',
validator(prop: string): boolean {
return ['button', 'submit', 'reset'].includes(prop);
}
}
});
console.log(props.title);
// =============================================================
const CUSTOM_EVENT_A = 'CUSTOM_EVENT_A';
// eslint-disable-next-line vue/valid-define-emits
const emit = defineEmits([CUSTOM_EVENT_A]); // 声明自定义事件
function triggerCustomEvent(event: MouseEvent) {
console.log('[child] before trigger custom event');
emit(CUSTOM_EVENT_A, event, count.value++); // 触发带参数自定义事件
console.log('[child] after trigger custom event');
}
// =============================================================
const attrs = useAttrs();
console.log(attrs['style']);
</script>
2 透传Attributes
父组件传递给子组件中未声明为 props 或 emits 的 attribute 或者 v-on 事件监听器
- 父组件
<template>
<!-- style和att属性未在子组件props中声明 -->
<MyPenetrate style="color: #ffffff" att="DEMO" @click="onClick" />
</template>
<script lang="ts" setup>
import MyPenetrate from './components/MyPenetrate.vue';
function onClick() {
console.log('onClick');
}
</script>
- 子组件
<template>
<button>click me</button>
</template>
<script lang="ts" setup></script>
- 渲染结果
<button data-v-7a7a37b1="" att="DEMO" style="color: rgb(255, 255, 255);">click me</button>
同时会执行onClick
函数
如果子组件也声明onclicke
事件,那么子组件和父组件的onclick
都会被触发(先执行子组件onclick,后执行父组件onclick)
3 禁止继承
- 子组件
<template>
<button>click me</button>
</template>
<script lang="ts" setup>
defineOptions({
inheritAttrs: false // 设置为false将禁止继承未声明的属性和事件
});
</script>
4 插槽 Slots
给子组件传递一些模板片段,让子组件在它们的组件中渲染这些片段
4.1 普通slot
- 子组件
<template>
<div class="fancy-div">
<slot name="header"></slot>
<slot name="main"></slot>
</div>
</template>
<script lang="ts" setup></script>
- 父组件
<template>
<NormalSlot>
<template v-slot:header>
<div class="header"></div>
</template>
<!-- v-slot简写为# -->
<template #main>
<div class="main"></div>
</template>
</NormalSlot>
</template>
<script lang="ts" setup>
import NormalSlot from './components/NormalSlot.vue';
</script>
<style>
.header {
width: 100%;
height: 60px;
background-color: cornflowerblue;
}
.main {
width: 100%;
height: 300px;
background-color: aqua;
}
</style>
- 渲染结果
<div class="fancy-div">
<div class="header"></div>
<div class="main"></div>
</div>
4.2 向父组件传参
- 子组件
<template>
<div class="fancy-div">
<slot name="header" :text="text"></slot>
<slot name="main"></slot>
<slot :message="message" :code="code"></slot>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const text = ref('This is TEXT');
const message = ref('This is MESSAGE');
const code = ref(100);
</script>
- 父组件
<template>
<SlotWithParam>
<template #header="headerProps">
<div class="header">{{ headerProps.text }}</div>
</template>
<!-- v-slot简写为# -->
<template #main>
<div class="main"></div>
</template>
<!-- 匿名slot -->
<template v-slot="{ message, code }"> {{ message }} = {{ code }} </template>
</SlotWithParam>
</template>
<script lang="ts" setup>
import SlotWithParam from './components/SlotWithParam.vue';
</script>
<style scoped>
.header {
width: 100%;
height: 60px;
background-color: cornflowerblue;
}
.main {
width: 100%;
height: 300px;
background-color: aqua;
}
</style>
5 依赖注入
**场景:**组件属性props 多级透传
**方法:**provide(提供者,根级组件)、inject(使用者,子孙级组件)
**关键字:**provide,inject,readonly(声明为只读,防止子孙进行修改)
- main.js
app.provide('appProvideMessage', 'APP_PROVIDE_MESSAGE'); // 应用层 Provide,全局可使用inject读取
- ProvideInjectView.vue
<template>
<ProvideInjectFirstChild />
</template>
<script lang="ts" setup>
import { provide, readonly, ref } from 'vue';
import ProvideInjectFirstChild from './components/ProvideInjectFirstChild.vue';
// provide(<注入名>, <值>);
provide('message1', 'MESSAGE_1');
// 值可以是任意类型,包括响应式的状态,比如一个 ref
const message2 = ref('MESSAGE_2');
provide('message2', message2);
const message3 = ref('MESSAGE_3');
provide('message3', message3);
const message4 = ref('MESSAGE_4');
provide('message4', message4);
const message5 = ref('MESSAGE_5');
provide('message5', readonly(message5));
const message6 = ref('MESSAGE_6');
provide('message6', readonly(message6));
</script>
- ProvideInjectFirstChild.vue
<template>
[FirstChild] appProvideMessage = {{ appProvideMessage }}<br />
[FirstChild] message1 = {{ message1 }}<br />
[FirstChild] message1_1 = {{ message1_1 }}<br />
[FirstChild] message2 = {{ message2 }}<br />
[FirstChild] message3 = {{ message3 }}<br />
[FirstChild] message4 = {{ message4 }}<br />
[FirstChild] message5 = {{ message5 }}<br />
[FirstChild] message6 = {{ message6 }}<br />
[FirstChild] message7 = {{ message7 }}<br />
<br /><br /><br />
<ProvideInjectSecondChild />
</template>
<script lang="ts" setup>
import ProvideInjectSecondChild from './ProvideInjectSecondChild.vue';
import { inject, provide, ref } from 'vue';
const appProvideMessage = inject('appProvideMessage'); // 应用层 Provide
var message1 = inject('message1'); // 不带默认值
message1 = 'ROOT_MESSAGE'; // 会更新本界面值,但不会更新子孙provide的值
const message1_1 = inject('message1', ref('MESSAGE1_DEFAULT')); // 类型不同,不会抛错
// message1_1.value = 'MESSAGE1_DEFAULT_REF'; // TypeError: Cannot create property 'value' on string 'MESSAGE_1'
var message2 = inject('message2', 'MESSAGE2_DEFAULT'); // 带默认值
message2 = 'MESSAGE2_CHANGED'; // 会更新本界面值,但不会更新子孙
const message3 = inject('message3', ref('MESSAGE2_DEFAULT_REF'));
message3.value = 'MESSAGE_4_REF_OVERRIDE'; // 修改ref,会更新子孙
const message4 = inject('message4');
provide('message4', 'MESSAGE_4_OVERRIDE'); // override,会更新子孙
const message5 = inject('message5', ref('MESSAGE5_DEFAULT_READONLY_REF'));
provide('message5', 'MESSAGE_5_OVERRIDE_READONLY_REF'); // override readonly,会更新子孙
const message6 = inject('message6', ref('MESSAGE6_DEFAULT_READONLY_REF'));
message6.value = 'MESSAGE_6_OVERRIDE_READONLY_REF'; // change readonly,无效(本页面和子孙都不会更新)
const message7 = inject('message7', ref('MESSAGE7_DEFAULT')); // 祖辈未提供,将采用默认值
</script>
- ProvideInjectSecondChild.vue
<template>
<ProvideInjectGrandChild />
</template>
<script lang="ts" setup>
import ProvideInjectGrandChild from './ProvideInjectGrandChild.vue';
</script>
- ProvideInjectGrandChild.vue
<template>
[GrandChild] appProvideMessage = {{ appProvideMessage }}<br />
[GrandChild] message1 = {{ message1 }}<br />
[GrandChild] message1_1 = {{ message1_1 }}<br />
[GrandChild] message2 = {{ message2 }}<br />
[GrandChild] message3 = {{ message3 }}<br />
[GrandChild] message4 = {{ message4 }}<br />
[GrandChild] message5 = {{ message5 }}<br />
[GrandChild] message6 = {{ message6 }}<br />
</template>
<script lang="ts" setup>
import { inject } from 'vue';
const appProvideMessage = inject('appProvideMessage'); // 应用层 Provide
const message1 = inject('message1');
const message1_1 = inject('message1');
const message2 = inject('message2');
const message3 = inject('message3');
const message4 = inject('message4');
const message5 = inject('message5');
const message6 = inject('message6');
</script>
- 结果