git: https://github.com/hi-cooper/vue-learning.git

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>
  • 结果