准备拿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,此时 this 为 undefined(不能访问 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>
|
三、核心总结
- 生命周期:组合式 API 用
onXXX 钩子替代选项式 API,setup 覆盖 beforeCreate/created,卸载阶段务必清理副作用;
- 响应式:基础类型用
ref(需 .value),复杂类型用 reactive,解构用 toRefs;
- 模板语法:多
v-model、v-slot 统一插槽是核心升级;
- 组件通信:
<script setup> 中用 defineProps/defineEmits 替代 props/emits 选项,跨层级用 provide/inject。