Vue3 组件开发:生命周期钩子,组件之间通信全方案

79 阅读1分钟

一、生命周期钩子(组合式 API)

1.1 钩子总览

组合式钩子对应选项式触发时机典型业务
onBeforeMountbeforeMount挂载前极少用
onMountedmounted挂载完成,DOM 可访问操作 DOM、初始化图表
onBeforeUpdatebeforeUpdate数据变化、DOM 更新前更新前处理
onUpdatedupdatedDOM 更新后依赖 DOM 的操作(慎用)
onBeforeUnmountbeforeDestroy卸载前清理定时器、解绑事件
onUnmounteddestroyed卸载后收尾

关键变化:组合式 API 没有 beforeCreate / created——setup 本身就在创建阶段执行,作用等同二者。钩子必须在 setup 顶层同步调用。

<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount } from "vue";

const list = ref([]);
let timer: number | undefined;

onMounted(async () => {
  list.value = await fetchList();        // 请求数据
  timer = setInterval(() => {}, 1000);   // 启动定时器
});

onBeforeUnmount(() => {
  clearInterval(timer);                  // 必须清理,防内存泄漏
});
</script>

记忆口诀:onMounted 动 DOM 拉数据、onBeforeUnmount 清资源。keep-alive 缓存组件还有 onActivated / onDeactivated。


二、父传子:props

2.1 defineProps 声明与类型校验

<!-- 子组件 Child.vue -->
<script setup lang="ts">
// 类型声明(编译期校验 + IDE 提示)
interface User {
  name: string;
  age: number;
}

// 方式一:纯 TS 类型
const props = defineProps<{
  title: string;
  user: User;
  count?: number;              // 可选
}>();

// 方式二:运行时校验 + 默认值(withDefaults)
const props2 = withDefaults(
  defineProps<{ count?: number; list: string[] }>(),
  {
    count: 0,
    list: () => [],            // 对象/数组默认值用函数返回
  }
);
</script>

<template>
  <h3>{{ title }}</h3>
  <p>{{ user.name }}</p>
</template>
<!-- 父组件 -->
<child title="标题" :user="userInfo" :count="5" />

2.2 单向数据流

props 是只读的,子组件不能修改(修改会告警且不生效)。要派生值用 computed,要改父数据用 $emit / defineModel。


三、子传父:defineEmits 自定义事件

<!-- 子组件 -->
<script setup lang="ts">
const emit = defineEmits<{
  change: [value: number];                    // 事件名 + 参数类型
  submit: [payload: { name: string }];
}>();

function handleClick() {
  emit("change", 42);                          // 触发事件并传参
}
</script>

<template>
  <button @click="handleClick">上报</button>
</template>
<!-- 父组件 -->
<script setup lang="ts">
function handleChange(val: number) {
  console.log("子组件上报:", val);
}
</script>

<template>
  <child @change="handleChange" />
</template>

四、组件双向绑定 defineModel(Vue 3.4+)

4.1 基本用法

<!-- 子组件:可双向绑定的输入框 -->
<script setup lang="ts">
const model = defineModel<string>();       // 声明双向绑定值

function clear() {
  model.value = "";
}
</script>

<template>
  <input v-model="model" placeholder="输入内容" />
</template>
<!-- 父组件 -->
<script setup lang="ts">
import { ref } from "vue";
const keyword = ref("");
</script>

<template>
  <my-input v-model="keyword" />
</template>

4.2 多个 v-model 与默认值

const visible = defineModel<boolean>("visible", { default: false });  // v-model:visible
const title = defineModel<string>("title");                            // v-model:title

底层等价于 modelValue + update:modelValue(或具名 xxx + update:xxx),Vue 3.4 起用 defineModel 一行搞定。


五、跨层级 provide / inject

解决祖先 → 任意后代的通信,免去逐层传 props。

// 祖先组件
import { ref, provide } from "vue";

const theme = ref("light");
const updateTheme = (val: string) => (theme.value = val);

provide("theme", theme);          // 提供响应式数据
provide("updateTheme", updateTheme);   // 提供修改函数(避免后代直接改)
// 后代组件(任意层级)
import { inject } from "vue";

const theme = inject<Ref<string>>("theme", ref("light"));   // 第二个参数是默认值
const updateTheme = inject<(v: string) => void>("updateTheme", () => {});
要点说明
响应式提供 ref(或 reactive + toRefs),不要提供普通值(会失去响应)
修改方式同时 provide 修改函数,遵循单向数据流
键冲突用 Symbol 或字符串常量统一管理

六、踩坑:props 只读、类型校验失效

坑现象解决
直接修改 props告警 + 修改不生效computed 派生 / defineModel / emit
props 类型校验"失效"运行时无报错(TS 类型是编译期)需要运行时校验用 withDefaults + 自定义 validator,或 defineProps 对象写法
defineModel 不生效报"没有此宏"升级 Vue 到 3.4+
provide 普通值后代修改不同步提供 ref 并传修改函数
inject 拿不到父级未 provide 或键不一致检查键名 + 提供默认值兜底
emit 事件名大小写@myEvent 与 my-event 不匹配统一用 kebab-case(my-event)

七、总结

  • 生命周期:6 个组合式钩子,setup 取代 beforeCreate/created,卸载前必清理;
  • 父传子:defineProps + TS 类型 / withDefaults 默认值,只读单向流;
  • 子传父:defineEmits 类型化声明 + $emit 传参;
  • 双向绑定:defineModel(3.4+)一行实现,支持多个与默认值;
  • 跨层:provide + inject(传 ref + 修改函数,保持单向流);
  • 踩坑:props 只读、运行时校验、3.4 版本要求、事件命名 kebab-case。