Vue生命周期

准备拿uniapp做一个app,学习一下vue的生命周期。

因为是一个全新的项目,所以使用vue3版本。

一、Vue3 生命周期详解

Vue3 兼容 Vue2 的选项式 API 生命周期,同时针对组合式 API(<script setup> 设计了更贴合的钩子函数,核心生命周期阶段不变,但使用方式有差异。

1、生命周期总览(阶段 + 触发机智)

生命周期阶段 选项式 API(Vue2/Vue3 通用) 组合式 API(Vue3 <script setup> 触发时机
实例创建前 beforeCreate 无(setup 函数替代) 组件实例创建前,数据观测、事件初始化未开始
实例创建后 created 无(setup 函数替代) 组件实例创建完成,可访问 data/methods,但 DOM 未挂载
挂载前 beforeMount onBeforeMount 模板编译完成,即将挂载到 DOM,$el 未生成
挂载后 mounted onMounted 组件挂载到 DOM,可访问 / 操作 DOM 元素(如获取节点、初始化第三方库)
更新前 beforeUpdate onBeforeUpdate 响应式数据更新,DOM 即将重新渲染(可获取更新前的 DOM 状态)
更新后 updated onUpdated DOM 已完成重新渲染,注意避免在此修改数据(易触发无限更新)
卸载前 beforeUnmount onBeforeUnmount 组件即将卸载,仍可访问组件实例和 DOM(如清除定时器、取消事件监听)
卸载后 unmounted onUnmounted 组件完全卸载,实例、DOM、事件监听等全部销毁
错误捕获(新增) errorCaptured onErrorCaptured 捕获子组件 / 自身的渲染、生命周期钩子中的错误
激活(KeepAlive) activated onActivated 缓存组件(<keep-alive>)被激活时触发
失活(KeepAlive) deactivated onDeactivated 缓存组件被失活时触发

2、两种API用法示例

(1)选项式API(兼容Vue2,适合简单组件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<script>
export default {
// 实例创建阶段(setup 替代这两个钩子)
beforeCreate() {
console.log("beforeCreate:实例创建前");
},
created() {
console.log("created:实例创建后,可访问 data");
console.log(this.count); // 能拿到 data 中的 count
},
// 挂载阶段
beforeMount() {
console.log("beforeMount:即将挂载 DOM,$el 不存在");
},
mounted() {
console.log("mounted:DOM 挂载完成,可操作节点");
console.log(this.$el); // 能拿到组件根节点
},
// 更新阶段
beforeUpdate() {
console.log("beforeUpdate:数据更新,DOM 未更新");
},
updated() {
console.log("updated:DOM 已更新");
},
// 卸载阶段
beforeUnmount() {
console.log("beforeUnmount:组件即将卸载,清除定时器/事件");
clearInterval(this.timer); // 清理副作用
},
unmounted() {
console.log("unmounted:组件已卸载,实例销毁");
},
// 数据
data() {
return { count: 0, timer: null };
},
mounted() {
this.timer = setInterval(() => {
this.count++;
}, 1000);
},
};
</script>

(2)组合式 API(<script setup>,Vue3 推荐)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<script setup>
import { ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from "vue";

const count = ref(0);
let timer = null;

// 替代 beforeCreate/created:setup 函数内的代码在这两个钩子前执行
console.log("setup:等价于 beforeCreate + created");

// 挂载前
onBeforeMount(() => {
console.log("onBeforeMount:即将挂载 DOM");
});

// 挂载后
onMounted(() => {
console.log("onMounted:DOM 挂载完成");
timer = setInterval(() => {
count.value++;
}, 1000);
});

// 更新前
onBeforeUpdate(() => {
console.log("onBeforeUpdate:数据更新,DOM 未更");
});

// 更新后
onUpdated(() => {
console.log("onUpdated:DOM 已更新");
});

// 卸载前
onBeforeUnmount(() => {
console.log("onBeforeUnmount:即将卸载,清理定时器");
clearInterval(timer);
});

// 卸载后
onUnmounted(() => {
console.log("onUnmounted:组件已卸载");
});
</script>

<template>
<div>{{ count }}</div>
</template>

核心注意点

  • setup 函数是组合式 API 的入口,执行时机早于 beforeCreate,此时 thisundefined(不能访问 this);
  • 组合式 API 钩子需先导入再使用(如 import { onMounted } from 'vue');
  • 卸载阶段的 onBeforeUnmount 是清理副作用(定时器、事件监听、请求取消)的核心时机,务必处理。

二、Vue3 常见语法(组合式 API 为主)

Vue3 核心语法围绕 <script setup> 展开,以下是高频使用的语法点:

1. 响应式数据

类型 API 适用场景 示例
基础类型响应式 ref 数字 / 字符串 / 布尔等基础类型 const count = ref(0); count.value = 1;(模板中无需 .value
对象 / 数组响应式 reactive 复杂数据类型(对象 / 数组) const user = reactive({ name: '张三' }); user.name = '李四';
响应式解构 toRefs 解构 reactive 对象不丢失响应式 const { name } = toRefs(user);(解构后 name 仍为响应式)
只读响应式 readonly 禁止修改响应式数据 const readOnlyUser = readonly(user); readOnlyUser.name = '王五'; // 无效

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script setup>
import { ref, reactive, toRefs } from "vue";

// 基础类型响应式
const msg = ref("Hello Vue3");
// 复杂类型响应式
const state = reactive({
list: [1, 2, 3],
info: { age: 18 },
});
// 解构不丢失响应式
const { list, info } = toRefs(state);
</script>

<template>
<div>{{ msg }}</div>
<div>{{ list }}</div>
<div>{{ info.age }}</div>
</template>

2. 计算属性与监听

(1)计算属性 computed

支持只读 / 可写两种模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<script setup>
import { ref, computed } from "vue";

const count = ref(0);
// 只读计算属性
const doubleCount = computed(() => count.value * 2);
// 可写计算属性
const fullName = computed({
get() {
return `${firstName.value} ${lastName.value}`;
},
set(val) {
const [first, last] = val.split(" ");
firstName.value = first;
lastName.value = last;
},
});
const firstName = ref("张");
const lastName = ref("三");

// 触发修改
fullName.value = "李 四";
</script>
(2)监听 watch/watchEffect
API 特点 示例
watch 显式指定监听源,支持深度监听 / 立即执行 watch(count, (newVal, oldVal) => { console.log(newVal); }, { immediate: true, deep: true })
watchEffect 自动收集依赖,隐式监听,立即执行 watchEffect(() => { console.log(count.value); })(修改 count 自动触发)

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<script setup>
import { ref, reactive, watch, watchEffect } from "vue";

const count = ref(0);
const user = reactive({ name: "张三", age: 18 });

// 监听单个 ref
watch(count, (newVal, oldVal) => {
console.log("count 从", oldVal, "变到", newVal);
});

// 监听 reactive 对象(深度监听默认开启)
watch(user, (newVal) => {
console.log("user 变化", newVal);
});

// 监听对象单个属性
watch(() => user.name, (newName) => {
console.log("姓名变化", newName);
});

// watchEffect 自动监听
watchEffect(() => {
console.log("count 或 user.age 变化", count.value, user.age);
});
</script>

3. 模板语法增强

(1)多 v-model 绑定(替代 .sync
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- 子组件 -->
<script setup>
defineProps(["name", "age"]);
defineEmits(["update:name", "update:age"]);
</script>
<template>
<input :value="name" @input="$emit('update:name', $event.target.value)" />
<input :value="age" @input="$emit('update:age', $event.target.value)" />
</template>

<!-- 父组件 -->
<script setup>
import { ref } from "vue";
const name = ref("张三");
const age = ref(18);
</script>
<template>
<Child v-model:name="name" v-model:age="age" />
</template>
(2)插槽 v-slot 统一语法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!-- 子组件 -->
<template>
<div>
<!-- 默认插槽 -->
<slot />
<!-- 具名插槽 -->
<slot name="footer" :msg="message" />
</div>
</template>
<script setup>
const message = ref("来自子组件的消息");
</script>

<!-- 父组件 -->
<template>
<Child>
<!-- 默认插槽 -->
<div>默认插槽内容</div>
<!-- 具名插槽 + 作用域插槽 -->
<template v-slot:footer="slotProps">
<div>{{ slotProps.msg }}</div>
</template>
</Child>
</template>

4. 组件通信

(1)父传子:defineProps
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!-- 子组件 -->
<script setup>
// 方式1:简单声明
const props = defineProps(["title", "list"]);
// 方式2:带类型/默认值
const props = defineProps({
title: {
type: String,
required: true,
default: "默认标题",
},
list: {
type: Array,
default: () => [],
},
});
</script>
<template>
<div>{{ title }}</div>
</template>

<!-- 父组件 -->
<template>
<Child title="父组件标题" :list="[1,2,3]" />
</template>
(2)子传父:defineEmits
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!-- 子组件 -->
<script setup>
const emit = defineEmits(["change", "delete"]);
// 触发事件
const handleClick = () => {
emit("change", "子组件数据");
emit("delete", 123);
};
</script>
<template>
<button @click="handleClick">触发事件</button>
</template>

<!-- 父组件 -->
<template>
<Child @change="handleChange" @delete="handleDelete" />
</template>
<script setup>
const handleChange = (data) => {
console.log("接收子组件数据", data);
};
const handleDelete = (id) => {
console.log("删除ID", id);
};
</script>
(3)依赖注入:provide/inject(跨层级通信)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!-- 父组件(提供数据) -->
<script setup>
import { provide, ref } from "vue";
const theme = ref("dark");
// 提供响应式数据
provide("theme", theme);
// 提供方法
provide("changeTheme", (val) => {
theme.value = val;
});
</script>

<!-- 孙组件(注入数据) -->
<script setup>
import { inject } from "vue";
const theme = inject("theme", "light"); // 第二个参数是默认值
const changeTheme = inject("changeTheme");
</script>
<template>
<div>{{ theme }}</div>
<button @click="changeTheme('light')">切换主题</button>
</template>

5. 自定义指令

Vue3 统一了指令钩子(贴合组件生命周期):

1
2
3
4
5
6
7
8
9
10
11
<script setup>
// 自定义指令:v-focus(自动聚焦输入框)
const vFocus = {
mounted(el) {
el.focus(); // 元素挂载后聚焦
},
};
</script>
<template>
<input v-focus />
</template>

三、核心总结

  1. 生命周期:组合式 API 用 onXXX 钩子替代选项式 API,setup 覆盖 beforeCreate/created,卸载阶段务必清理副作用;
  2. 响应式:基础类型用 ref(需 .value),复杂类型用 reactive,解构用 toRefs
  3. 模板语法:多 v-modelv-slot 统一插槽是核心升级;
  4. 组件通信:<script setup> 中用 defineProps/defineEmits 替代 props/emits 选项,跨层级用 provide/inject