|
## 组件生命周期概览
Vue 组件的生命周期贯穿创建、挂载、更新和销毁阶段。以下为主要钩子(以 Composition API
写法为例):
1. **`setup()`**
- 在组件实例创建之初运行,是 Composition API 中的入口。
- 此阶段可以定义响应式状态、计算属性、方法、以及注册生命周期钩子。
- 不可访问 `this`,但可以通过返回的对象暴露数据给模板。
2. **`onBeforeMount()`**
- 在挂载开始之前调用,DOM 还未生成。
3. **`onMounted()`**
- 组件挂载完成后调用,DOM 已插入,可以安全地进行 DOM 操作或发起网络请求。
- 等同于 Options API 的 `mounted()`。
4. **`onBeforeUpdate()`**
- 在响应式数据改变并触发重新渲染之前调用。
5. **`onUpdated()`**
- 在组件的 DOM 更新完成后调用。
6. **`onBeforeUnmount()`**
- 组件卸载前调用,适合清理定时器、取消订阅等。
7. **`onUnmounted()`**
- 完全卸载后调用,组件实例及其绑定的指令已被移除。
|
|
`setup()` 是 Vue 3 Composition API 的**入口函数**,是组件中**第一个执行的钩子**。
```typescript
import { ref, computed } from 'vue';
export default {
setup() {
// 在这里定义响应式数据、计算属性、方法等
const count = ref(0);
const doubled = computed(() => count.value * 2);
function increment() {
count.value++;
}
// 返回的对象会暴露给模板
return {
count,
doubled,
increment
};
}
};
```
|
|
```
组件创建流程:
┌─────────────────────────────────────────────────────────┐
│ 1. beforeCreate() ← Vue 2 Options API │
│ └─ 此时 data、methods 等还未初始化 │
├─────────────────────────────────────────────────────────┤
│ 2. setup() ← Vue 3 Composition API │
│ └─ 在 beforeCreate 和 created 之间执行 │
│ └─ 此时无法访问 this │
│ └─ 可以接收 props 和 context │
├─────────────────────────────────────────────────────────┤
│ 3. created() ← Vue 2 Options API │
│ └─ 此时 data、methods 已初始化 │
├─────────────────────────────────────────────────────────┤
│ 4. onBeforeMount() │
│ └─ 挂载开始之前 │
├─────────────────────────────────────────────────────────┤
│ 5. onMounted() │
│ └─ DOM 已挂载完成 │
└─────────────────────────────────────────────────────────┘
```
|
|
```typescript
export default {
setup(props, context) {
// props: 响应式的 props 对象
console.log(props.msg);
// context: 包含三个可选属性
console.log(context.attrs); // 非响应式的 attrs 对象
console.log(context.slots); // 插槽对象
console.log(context.emit); // 触发事件的函数
// 返回值会暴露给模板
return {};
}
};
```
|
|
### 简写形式:`<script setup>`
Vue 3.2+ 推荐使用 `<script setup>` 语法糖,更简洁:
```vue
<script setup lang="ts">
import { ref, computed } from 'vue';
// 直接定义响应式数据,无需 return
const count = ref(0);
const doubled = computed(() => count.value * 2);
function increment() {
count.value++;
}
// 定义 props
defineProps<{
msg: string;
}>();
// 定义 emits
const emit = defineEmits<{
(e: 'update', value: number): void;
}>();
</script>
<template>
<div>{{ count }} x 2 = {{ doubled }}</div>
<button @click="increment">增加</button>
</template>
```
|
|
### `setup()` 的限制
| 限制 | 说明 | 解决方案 |
|------|------|---------|
| 无法访问 `this` | `setup()` 在创建实例前执行 | 使用返回的对象或 `getCurrentInstance()` |
| 必须同步执行 | 不能是 async 函数 | 在 `onMounted` 中执行异步操作 |
| 必须返回对象 | 返回非对象会报错 | 确保返回普通对象或使用 `<script setup>` |
|
|
### `setup()` vs Options API
```vue
<!-- Options API -->
<script>
export default {
data() {
return {
count: 0
};
},
computed: {
doubled() {
return this.count * 2;
}
},
methods: {
increment() {
this.count++;
}
},
mounted() {
console.log('组件已挂载');
}
};
</script>
```
```vue
<!-- Composition API -->
<script setup>
import { ref, computed, onMounted } from 'vue';
const count = ref(0);
const doubled = computed(() => count.value * 2);
function increment() {
count.value++;
}
onMounted(() => {
console.log('组件已挂载');
});
</script>
```
### 实际示例:对比两种写法
#### Options API 写法
```vue
<script>
export default {
props: {
initialCount: {
type: Number,
default: 0
}
},
data() {
return {
count: this.initialCount,
loading: false
};
},
computed: {
doubled() {
return this.count * 2;
}
},
watch: {
count(newVal) {
console.log('count changed:', newVal);
}
},
methods: {
async fetchData() {
this.loading = true;
// 模拟 API 调用
await new Promise(resolve => setTimeout(resolve, 1000));
this.count = 10;
this.loading = false;
}
},
mounted() {
this.fetchData();
}
};
</script>
```
#### Composition API 写法
```vue
<script setup>
import { ref, computed, watch, onMounted } from 'vue';
const props = defineProps<{
initialCount?: number;
}>();
const count = ref(props.initialCount || 0);
const loading = ref(false);
const doubled = computed(() => count.value * 2);
watch(count, (newVal) => {
console.log('count changed:', newVal);
});
async function fetchData() {
loading.value = true;
// 模拟 API 调用
await new Promise(resolve => setTimeout(resolve, 1000));
count.value = 10;
loading.value = false;
}
onMounted(() => {
fetchData();
});
</script>
```
|
|
### `setup()` 的最佳实践
#### 1. 使用 Composition API 组织逻辑
```vue
<script setup>
import { useUserStore } from '@/stores/user';
import { useRouter } from 'vue-router';
// 按功能分组
const store = useUserStore();
const router = useRouter();
// 响应式状态
const count = ref(0);
// computed 创建了一个计算属性,建立了响应式连接
const user = computed(() => store.currentUser);
// 方法
function increment() {
count.value++;
}
function logout() {
store.logout();
router.push('/login');
}
// 生命周期
onMounted(() => {
console.log('组件已挂载');
});
</script>
```
#### ⚠️ 重要:为什么 store 属性需要使用 `computed`?
```vue
<script setup>
import { useUserStore } from '@/stores/user';
const store = useUserStore();
// ❌ 错误:直接赋值会丢失响应性
// const user = store.currentUser; // 只是当前时刻的值快照
// 当 store.currentUser 改变时,user 不会更新
// ✅ 正确:使用 computed 保持响应性
const user = computed(() => store.currentUser);
// 当 store.currentUser 改变时,user 会自动重新计算并更新
</script>
```
| 写法 | 问题 | 说明 |
|------|------|------|
| `const user = store.currentUser` | **丢失响应性** | 只获取当前时刻的值,后续 store 变化不会反映到 user |
| `const user = computed(() => store.currentUser)` | **保持响应性** | 建立响应式连接,store 变化时自动更新 |
**原因:**
- `store.currentUser` 是一个对象属性,不是 ref/reactive
- 直接赋值只是复制了引用,没有建立响应式依赖关系
- `computed` 会创建一个 getter 追踪器,当依赖的 `store.currentUser` 变化时会自动触发重新计算
- 这样模板中使用 `{{ user }}` 时,就能实时显示最新的值
#### 2. 使用 Composables 复用逻辑
```typescript
// composables/useCounter.js
import { ref, computed } from 'vue';
export function useCounter(initial = 0) {
const count = ref(initial);
const doubled = computed(() => count.value * 2);
function increment() {
count.value++;
}
return {
count,
doubled,
increment
};
}
```
```vue
<!-- 在组件中使用 -->
<script setup>
import { useCounter } from '@/composables/useCounter';
const { count, doubled, increment } = useCounter(10);
</script>
```
#### 3. 异步操作放在 `onMounted` 中
```vue
<script setup>
import { ref, onMounted } from 'vue';
const data = ref([]);
const loading = ref(false);
// ? 错误:setup 不能是 async
// export default {
// async setup() {
// const res = await fetch(...);
// }
// }
// ? 正确:在 onMounted 中执行异步操作
onMounted(async () => {
loading.value = true;
const res = await fetch('/api/data');
data.value = await res.json();
loading.value = false;
});
</script>
```
### 总结对比表
| 特性 | Options API | Composition API (`<script setup>`) |
|------|-------------|-----------------------------------|
| 代码组织 | 按选项分组(data、methods、computed) | 按功能逻辑组织 |
| `this` 访问 | 需要 | 不需要 |
| 类型推断 | 较弱 | 强(配合 TypeScript) |
| 代码复用 | mixins(容易冲突) | composables(清晰) |
| 学习曲线 | 较低 | 稍高 |
| 代码量 | 较多 | 较少 |
| 推荐场景 | 简单组件 | 复杂组件、逻辑复用 |
### 迁移建议
```vue
<!-- 从 Options API 迁移到 Composition API -->
<!-- 之前 -->
<script>
export default {
props: ['msg'],
data() {
return {
count: 0
};
},
computed: {
doubled() {
return this.count * 2;
}
},
methods: {
increment() {
this.count++;
}
}
};
</script>
<!-- 之后 -->
<script setup>
import { ref, computed } from 'vue';
const props = defineProps<{ msg: string }>();
const count = ref(0);
const doubled = computed(() => count.value * 2);
function increment() {
count.value++;
}
</script>
```
|
|
### 关键要点
1. **`setup()` 是组件的入口**,在创建阶段最早执行
2. **`<script setup>` 是推荐语法**,更简洁且类型友好
3. **无法访问 `this`**,所有数据通过 `ref`、`computed` 等 API 定义
4. **异步操作放在 `onMounted`**,不要让 `setup` 成为 async 函数
5. **使用 Composables 复用逻辑**,替代 mixins
6. **按功能组织代码**,而不是按选项类型
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Vue 3 组件生命周期与 `onMounted` 使用说明
本文件解释了以下片段的意义,并结合 Vue 3 的组件加载生命周期说明 `onMounted` 钩子的工作方式:
```js
// 组件挂载时获取数据
onMounted(() => {
getDatakuList(1, 20)
fetchTableData()
})
```
## 代码含义
这段代码使用了 Vue 3 Composition API 中的生命周期钩子 `onMounted`。
- `onMounted` 是一个 **组合式 API(Composition API)钩子**,它接受一个回调函数。
- 当组件的 **DOM 节点已插入到页面文档中**(即挂载完成)后,Vue 会调用这个回调。
在上述例子中,组件在挂载完成后立即执行 `getDatakuList` 和 `fetchTableData` 函数,以
从后端或本地资源加载数据并填充组件所需的列表/表格。这是一种常见的模式,用于初始化
组件状态或者触发异步请求。
|
|
## `onMounted` 是 Vue 3 的新用法吗?
- 是的,`onMounted` 是 Vue 3 Composition API 提供的生命周期钩子之一。
- 在 Vue 2 的 Options API 中,相应的生命周期钩子是 `mounted()`,它是以组件选项对象的
方法形式声明的:
```js
export default {
mounted() {
// ...
}
}
```
- Vue 3 仍然支持 Options API,`mounted()` 依旧可用;但 Composition API 推荐在 `setup()`
函数中使用 `onMounted()`,它可以与其他逻辑更好地组合和复用。
|
|
### 属性挂载
不是“所有组件属性的挂载都在这个方法中”。组件属性(prop)是传递给子组件的外部数据,
它们在组件创建阶段即可访问(在 `setup()` 中通过参数得到)。`onMounted` 只是在 DOM
插入后运行的钩子,适用于需要延迟执行的初始化逻辑(例如依赖于浏览器环境或需要
访问渲染结果)。
## 总结
- `onMounted()` 是 Vue 3 Composition API 钩子,代表组件挂载完成的生命周期时刻。
- 它不是挂载所有属性的地方,只是一个执行初始化代码的时机。
- 结合生命周期可以在不同阶段插入逻辑,例如在 `setup` 中声明状态、在 `onMounted`
发起网络请求,以及在 `onUnmounted` 清理资源。
|
|
|
|
|