setup
## 组件生命周期概览 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. **按功能组织代码**,而不是按选项类型 ---

 


 


 


 


onMounted
# 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` 清理资源。
参考