'# 【vuejs】首次页面加载时触发那些声明周期钩子函数
一、背景与问题
在Vue开发中,组件的生命周期钩子是控制组件行为的关键机制。首次页面加载时,开发者常常需要通过created、mounted等钩子函数完成数据初始化、DOM操作、第三方库初始化等关键任务。但实际开发中,开发者常遇到以下问题:
- 不理解钩子函数的执行顺序
- 在
created中进行DOM操作导致的错误 - 在
mounted中执行大量计算导致的性能问题 - 未正确处理异步数据的加载顺序
- 动态组件加载时的特殊行为
本文将深入解析Vue组件在首次加载时的生命周期行为,结合实际开发场景,探讨最佳实践和常见陷阱。
二、基本原理
Vue组件的生命周期分为四个阶段:创建(Creation)、挂载(Mounting)、更新(Updating)、销毁(Destroying)。首次页面加载时,主要涉及创建和挂载阶段的钩子函数。
1. 创建阶段(Creation)
beforeCreate:组件实例刚被创建,还未初始化data和propscreated:data和props已初始化,但尚未挂载DOM
2. 挂载阶段(Mounting)
beforeMount:模板编译完成,但尚未渲染到DOMmounted:模板已渲染到DOM,可以进行DOM操作
3. 渲染机制
Vue通过虚拟DOM技术实现高效更新。首次加载时,Vue会:
- 将模板编译为渲染函数
- 通过
vnode创建虚拟节点 - 通过
diff算法计算最小更新 - 最终将结果渲染到真实DOM
三、环境准备
# 创建Vue3项目
npm create vue@latest
# 或使用Vite创建
npm init vite@latest四、核心实现
1. 基础生命周期钩子示例
<template>
<div class="app">
<p>当前时间:{{ time }}</p>
</div>
</template>
<script>
export default {
data() {
return {
time: null
};
},
created() {
console.log('created');
this.time = new Date().toLocaleString();
},
mounted() {
console.log('mounted');
this.time = new Date().toLocaleString();
}
};
</script>关键代码解释:
created钩子在组件实例创建后立即执行,此时data和props已初始化mounted钩子在DOM渲染完成后执行,此时可以安全操作DOM- 由于
mounted在created之后执行,两次time的值会不同
2. 异步数据加载示例
<template>
<div class="app">
<p>用户信息:{{ user }}</p>
</div>
</template>
<script>
export default {
data() {
return {
user: null
};
},
created() {
console.log('created');
this.loadUser();
},
async loadUser() {
try {
const response = await fetch('/api/user');
this.user = await response.json();
} catch (error) {
console.error('加载用户数据失败:', error);
}
}
};
</script>关键代码解释:
- 在
created中发起异步请求,避免在mounted中执行耗时操作 - 使用
async/await保证代码可读性 - 捕获异常避免页面崩溃
3. 动态组件加载示例
<template>
<div class="app">
<component :is="currentComponent" />
</div>
</template>
<script>
export default {
data() {
return {
currentComponent: null
};
},
created() {
console.log('created');
this.loadComponent();
},
methods: {
async loadComponent() {
try {
const { default: Comp } = await import('./DynamicComponent.vue');
this.currentComponent = Comp;
} catch (error) {
console.error('加载动态组件失败:', error);
}
}
}
};
</script>关键代码解释:
- 使用动态组件实现按需加载
- 在
created中进行组件加载,避免阻塞页面渲染 - 使用
import()实现动态导入
五、完整案例
1. 模拟用户登录页面
<template>
<div class="login-page">
<h1>用户登录</h1>
<form @submit.prevent="submitForm">
<div>
<label>用户名:</label>
<input v-model="username" type="text" required />
</div>
<div>
<label>密码:</label>
<input v-model="password" type="password" required />
</div>
<button type="submit">登录</button>
</form>
<div v-if="errorMessage" class="error">{{ errorMessage }}</div>
</div>
</template>
<script>
export default {
data() {
return {
username: '',
password: '',
errorMessage: ''
};
},
created() {
console.log('created');
this.checkAuthStatus();
},
mounted() {
console.log('mounted');
this.initForm();
},
methods: {
initForm() {
this.username = '';
this.password = '';
this.errorMessage = '';
},
async checkAuthStatus() {
try {
const response = await fetch('/api/auth/status');
if (response.status === 401) {
this.errorMessage = '请先登录';
}
} catch (error) {
console.error('检查认证状态失败:', error);
}
},
async submitForm() {
try {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: this.username, password: this.password })
});
if (!response.ok) {
throw new Error('登录失败');
}
const data = await response.json();
console.log('登录成功:', data);
this.initForm();
} catch (error) {
this.errorMessage = '登录失败,请检查用户名和密码';
console.error('登录错误:', error);
}
}
}
};
</script>
<style scoped>
.login-page {
max-width: 400px;
margin: 50px auto;
padding: 20px;
border: 1px solid #ccc;
border-radius: 8px;
}
.error {
color: red;
margin-top: 10px;
}
</style>关键代码解释:
- 在
created中检查用户认证状态,避免重复登录 - 在
mounted中初始化表单状态 - 使用异步处理登录请求,处理错误情况
- 使用scoped CSS保证样式局部作用域
六、源码解析
Vue的生命周期钩子执行顺序由其内部的mount方法控制,关键代码如下(简化版):
function mountComponent (vm, isInitialMount) {
// ...其他代码...
if (isInitialMount) {
callHook(vm, 'created');
if (vm.$options.mounted) {
callHook(vm, 'mounted');
}
}
}在首次加载时,isInitialMount为true,会依次触发created和mounted钩子。
七、进阶使用
1. 混合使用生命周期钩子
<template>
<div>
<p>组件状态:{{ status }}</p>
</div>
</template>
<script>
export default {
data() {
return {
status: '初始化中'
};
},
created() {
this.status = '已创建';
this.initData();
},
mounted() {
this.status = '已挂载';
this.startTimer();
},
methods: {
initData() {
// 异步数据初始化
},
startTimer() {
// 启动定时器
}
}
};
</script>2. 懒加载组件
<template>
<div>
<LazyComponent v-if="isLoaded" />
</div>
</template>
<script>
export default {
data() {
return {
isLoaded: false
};
},
created() {
this.loadComponent();
},
methods: {
async loadComponent() {
try {
const { default: Comp } = await import('./LazyComponent.vue');
this.isLoaded = true;
} catch (error) {
console.error('加载组件失败:', error);
}
}
}
};
</script>八、性能与工程实践
1. 性能优化策略
| 问题 | 解决方案 |
|---|---|
mounted中执行大量计算 | 使用nextTick批量处理 |
| 频繁更新DOM | 使用v-if替代v-show |
| 大量异步请求 | 使用防抖/节流控制请求频率 |
2. 安全风险防范
- 模板注入:避免直接拼接用户输入
- XSS攻击:使用
v-sanitize或DOMPurify处理用户输入 - 数据泄露:在
created中处理敏感数据时,使用v-if控制显示
3. 异常处理规范
try {
await someAsyncOperation();
} catch (error) {
console.error('处理错误:', error);
this.errorMessage = '发生未知错误';
}九、常见问题与踩坑
1. 常见错误
| 错误场景 | 错误示例 | 解决方案 |
|---|---|---|
在created中操作DOM | mounted中未正确处理异步数据 | 使用nextTick处理异步更新 |
| 未处理异步错误 | 忽略try/catch块 | 添加错误处理逻辑 |
滥用mounted | 频繁操作DOM导致性能问题 | 使用v-if控制渲染 |
2. 真实开发案例
某电商平台在mounted中直接操作DOM导致页面卡顿,通过以下优化解决:
// 优化前
mounted() {
this.$el.querySelector('.product-list').scrollIntoView();
}
// 优化后
mounted() {
this.$nextTick(() => {
this.$el.querySelector('.product-list').scrollIntoView();
});
}十、最佳实践
1. 使用规范
created:初始化数据、注册事件、发起异步请求mounted:执行DOM操作、初始化第三方库、处理页面布局beforeMount:进行模板编译前的预处理- 避免在
mounted中进行复杂计算
2. 性能建议
- 使用
v-if替代v-show进行条件渲染 - 使用
keep-alive缓存动态组件 - 对大型组件使用
v-once避免重复渲染
3. 安全建议
- 对用户输入进行严格的验证和过滤
- 使用
v-sanitize处理富文本内容 - 在敏感数据处理时使用
v-if控制显示
十一、总结
Vue组件的首次加载生命周期是开发中的关键阶段,理解created和mounted钩子的执行顺序和差异对于构建高性能、可维护的应用至关重要。通过合理使用生命周期钩子,可以有效管理组件状态、处理异步操作、优化性能并确保安全。
开发时需注意:
- 避免在
created中进行DOM操作 - 在
mounted中处理DOM相关逻辑 - 对异步操作进行合理错误处理
- 遵循组件职责分离原则
- 根据具体场景选择合适的生命周期钩子
通过深入理解生命周期机制,开发者可以更高效地构建复杂应用,避免常见的性能陷阱和安全风险。