❌ 错误写法
module.exports = {
mounted() { ... } // 这不是 Vue 实例的选项对象!
}
✅ 正确写法
module.exports = {
data() { ... },
mounted() { ... }, // 这是 Vue 组件的选项
methods: { ... }
};
1. Vue 2 + CommonJS 写法
基础示例
components/Counter.vue.js
// Vue 单文件组件的 CommonJS 写法
module.exports = {
// ========== 数据 ==========
data() {
return {
count: 0,
message: ''
};
},
// ========== 计算属性 ==========
computed: {
doubleCount() {
return this.count * 2;
}
},
// ========== 方法 ==========
methods: {
increment() {
this.count++;
},
fetchData() {
console.log('获取数据...');
}
},
// ========== 生命周期钩子 - mounted ==========
mounted() {
console.log('组件已挂载到 DOM ✓');
// 1. 访问 DOM 元素
console.log('DOM 元素:', this.$el);
// 2. 初始化第三方库
this.initChart();
// 3. 发送 AJAX 请求
this.fetchData();
// 4. 添加事件监听
window.addEventListener('resize', this.handleResize);
// 5. 启动定时器
this.timer = setInterval(() => {
console.log('定时器运行中...');
}, 1000);
},
// ========== 销毁前的清理工作 ==========
beforeDestroy() {
// 清除定时器
if (this.timer) {
clearInterval(this.timer);
}
// 移除事件监听
window.removeEventListener('resize', this.handleResize);
},
methods: {
initChart() {
console.log('初始化图表...');
// 这里可以初始化 ECharts、Chart.js 等
},
handleResize() {
console.log('窗口大小改变:', window.innerWidth, window.innerHeight);
}
}
};
2. 完整实战示例
项目结构
my-vue-app/
├── package.json
├── server.js # Node.js 服务器
├── vue-loader.js # Vue 加载器模拟
├── components/
│ ├── UserList.js # 用户列表组件
│ ├── ChartWidget.js # 图表组件
│ └── ModalDialog.js # 弹窗组件
└── pages/
└── dashboard.js # 仪表盘页面
UserList.js - 用户列表组件
module.exports = {
name: 'UserList',
props: {
users: {
type: Array,
required: true
}
},
data() {
return {
loading: false,
selectedUser: null,
searchKeyword: ''
};
},
computed: {
filteredUsers() {
return this.users.filter(user =>
user.name.toLowerCase().includes(this.searchKeyword.toLowerCase())
);
}
},
methods: {
selectUser(user) {
this.selectedUser = user;
this.$emit('user-selected', user);
},
async deleteUser(userId) {
if (confirm('确定删除此用户?')) {
this.loading = true;
try {
await this.$http.delete(`/api/users/${userId}`);
this.$emit('user-deleted', userId);
} catch (error) {
console.error('删除失败:', error);
} finally {
this.loading = false;
}
}
}
},
// ========== MOUNTED 生命周期 ==========
mounted() {
console.log('📋 UserList 组件已挂载');
// 1. 初始化下拉刷新
this.initPullToRefresh();
// 2. 加载初始数据
this.loadInitialData();
// 3. 设置键盘快捷键
document.addEventListener('keydown', this.handleKeydown);
// 4. 滚动到上次位置
this.scrollToSavedPosition();
},
beforeDestroy() {
// 清理工作
document.removeEventListener('keydown', this.handleKeydown);
},
methods: {
initPullToRefresh() {
console.log('🔄 初始化下拉刷新');
// 移动端下拉刷新逻辑
},
async loadInitialData() {
console.log('📊 加载初始数据...');
this.loading = true;
try {
// 模拟 API 调用
await new Promise(resolve => setTimeout(resolve, 500));
console.log('✅ 数据加载完成');
} catch (error) {
console.error('❌ 数据加载失败:', error);
} finally {
this.loading = false;
}
},
handleKeydown(event) {
// Ctrl+F 聚焦搜索框
if (event.ctrlKey && event.key === 'f') {
event.preventDefault();
this.$refs.searchInput?.focus();
}
},
scrollToSavedPosition() {
const savedPosition = localStorage.getItem('userListScrollPos');
if (savedPosition) {
window.scrollTo(0, parseInt(savedPosition));
}
}
}
};
ChartWidget.js - 图表组件
module.exports = {
name: 'ChartWidget',
props: {
chartType: {
type: String,
default: 'line'
},
chartData: {
type: Object,
required: true
}
},
data() {
return {
chartInstance: null,
isLoading: true,
error: null
};
},
// ========== MOUNTED 生命周期 ==========
mounted() {
console.log('📈 ChartWidget 组件已挂载');
// 1. 等待 DOM 完全渲染
this.$nextTick(() => {
console.log('✅ DOM 渲染完成,初始化图表');
this.initChart();
});
// 2. 监听窗口大小变化
window.addEventListener('resize', this.handleResize);
// 3. 监听数据变化
this.$watch('chartData', (newData) => {
console.log('📊 数据更新,重绘图表');
this.updateChart(newData);
}, { deep: true });
},
beforeDestroy() {
// 清理图表实例
if (this.chartInstance) {
this.chartInstance.dispose();
this.chartInstance = null;
}
window.removeEventListener('resize', this.handleResize);
},
methods: {
initChart() {
// 动态加载 ECharts
if (typeof echarts !== 'undefined') {
this.chartInstance = echarts.init(this.$el);
this.renderChart();
this.isLoading = false;
} else {
// 如果 ECharts 未加载,动态引入
this.loadECharts();
}
},
loadECharts() {
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js';
script.onload = () => {
this.chartInstance = echarts.init(this.$el);
this.renderChart();
this.isLoading = false;
};
script.onerror = () => {
this.error = '图表库加载失败';
this.isLoading = false;
};
document.head.appendChild(script);
},
renderChart() {
if (!this.chartInstance) return;
const option = {
title: { text: this.chartType + ' 图表' },
tooltip: { trigger: 'axis' },
xAxis: { type: 'category', data: this.chartData.categories },
yAxis: { type: 'value' },
series: [{
data: this.chartData.values,
type: this.chartType
}]
};
this.chartInstance.setOption(option);
},
updateChart(newData) {
if (this.chartInstance) {
this.chartInstance.setOption({
xAxis: { data: newData.categories },
series: [{ data: newData.values }]
});
}
},
handleResize() {
if (this.chartInstance) {
this.chartInstance.resize();
}
}
}
};
ModalDialog.js - 弹窗组件
module.exports = {
name: 'ModalDialog',
props: {
visible: {
type: Boolean,
default: false
},
title: {
type: String,
default: '提示'
},
width: {
type: String,
default: '500px'
}
},
data() {
return {
isVisible: this.visible,
isAnimating: false
};
},
watch: {
visible(newVal) {
this.isVisible = newVal;
if (newVal) {
this.openModal();
} else {
this.closeModal();
}
}
},
// ========== MOUNTED 生命周期 ==========
mounted() {
console.log('🪟 ModalDialog 组件已挂载');
// 1. 添加遮罩层样式
this.addOverlayStyles();
// 2. 绑定全局事件
document.addEventListener('keydown', this.handleEscKey);
document.body.style.overflow = 'hidden';
},
beforeDestroy() {
// 清理
document.removeEventListener('keydown', this.handleEscKey);
document.body.style.overflow = '';
},
methods: {
addOverlayStyles() {
if (!document.getElementById('modal-styles')) {
const style = document.createElement('style');
style.id = 'modal-styles';
style.textContent = `
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
}
.modal-content {
background: white;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
animation: modalFadeIn 0.3s ease;
}
@keyframes modalFadeIn {
from { opacity: 0; transform: scale(0.9); }
to { opacity: 1; transform: scale(1); }
}
`;
document.head.appendChild(style);
}
},
openModal() {
this.isAnimating = true;
console.log('📂 打开弹窗');
// 焦点管理 - 将焦点移到弹窗内
this.$nextTick(() => {
const firstInput = this.$el.querySelector('input, button');
firstInput?.focus();
});
},
closeModal() {
this.isAnimating = false;
console.log('📁 关闭弹窗');
this.$emit('close');
},
handleEscKey(event) {
if (event.key === 'Escape' && this.isVisible) {
this.closeModal();
}
},
confirm() {
this.$emit('confirm');
this.closeModal();
},
cancel() {
this.$emit('cancel');
this.closeModal();
}
}
};
3. 页面级组件 (pages/dashboard.js)
// 页面级组件
module.exports = {
name: 'DashboardPage',
components: {
'user-list': require('../components/UserList'),
'chart-widget': require('../components/ChartWidget'),
'modal-dialog': require('../components/ModalDialog')
},
data() {
return {
users: [
{ id: 1, name: '张三', email: 'zhangsan@example.com' },
{ id: 2, name: '李四', email: 'lisi@example.com' },
{ id: 3, name: '王五', email: 'wangwu@example.com' }
],
chartData: {
categories: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'],
values: [120, 200, 150, 80, 70]
},
showModal: false
};
},
// ========== MOUNTED 生命周期 ==========
mounted() {
console.log('🏠 Dashboard 页面已挂载');
// 1. 页面加载动画
this.hideLoadingAnimation();
// 2. 检查用户登录状态
this.checkAuthStatus();
// 3. 加载用户偏好设置
this.loadUserPreferences();
// 4. 初始化 WebSocket 连接
this.initWebSocket();
// 5. 记录页面访问
this.trackPageView();
},
beforeDestroy() {
// 断开 WebSocket
if (this.ws) {
this.ws.close();
}
},
methods: {
hideLoadingAnimation() {
const loader = document.getElementById('page-loader');
if (loader) {
loader.style.opacity = '0';
setTimeout(() => loader.remove(), 300);
}
},
checkAuthStatus() {
const token = localStorage.getItem('authToken');
if (!token) {
console.warn('⚠️ 用户未登录');
this.$router?.push('/login');
}
},
loadUserPreferences() {
const prefs = localStorage.getItem('dashboardPrefs');
if (prefs) {
const parsed = JSON.parse(prefs);
console.log('📋 加载用户偏好:', parsed);
}
},
initWebSocket() {
// 模拟 WebSocket 连接
console.log('🔌 初始化 WebSocket 连接');
/*
this.ws = new WebSocket('ws://localhost:8080');
this.ws.onopen = () => console.log('✅ WebSocket 已连接');
this.ws.onmessage = (event) => this.handleMessage(event.data);
*/
},
trackPageView() {
console.log('📊 记录页面访问:', window.location.pathname);
// 发送到分析服务
},
handleMessage(data) {
console.log('📨 收到消息:', data);
},
openAddUserModal() {
this.showModal = true;
},
handleUserAdded(user) {
this.users.push(user);
}
}
};
4. 生命周期钩子执行顺序
module.exports = {
name: 'LifecycleDemo',
data() {
console.log('① data(): 初始化数据');
return { message: 'Hello' };
},
beforeCreate() {
console.log('② beforeCreate: 实例刚创建,数据不可用');
},
created() {
console.log('③ created: 实例创建完成,可以访问 data');
// 适合: API 请求、初始化数据
},
beforeMount() {
console.log('④ beforeMount: 挂载开始,模板编译完成');
},
mounted() {
console.log('⑤ mounted: DOM 已挂载,可以操作 DOM');
// 适合: 初始化第三方库、DOM 操作、事件监听
},
beforeUpdate() {
console.log('⑥ beforeUpdate: 数据变化,DOM 更新前');
},
updated() {
console.log('⑦ updated: DOM 已更新');
},
beforeDestroy() {
console.log('⑧ beforeDestroy: 实例销毁前');
// 适合: 清理定时器、取消订阅、移除事件监听
},
destroyed() {
console.log('⑨ destroyed: 实例已销毁');
}
};
控制台输出顺序:
① data(): 初始化数据
② beforeCreate: 实例刚创建,数据不可用
③ created: 实例创建完成,可以访问 data
④ beforeMount: 挂载开始,模板编译完成
⑤ mounted: DOM 已挂载,可以操作 DOM
5. 常见问题与解决方案
| 问题 | 原因 | 解决方案 |
|---|---|---|
mounted is not defined |
不在 Vue 组件选项中 | 确保 mounted在 module.exports的对象内部 |
| DOM 操作无效 | DOM 还未渲染 | 使用 this.$nextTick()或放在 mounted中 |
| 内存泄漏 | 未清理事件监听/定时器 | 在 beforeDestroy中清理 |
this指向错误 |
箭头函数问题 | 使用普通函数或在 methods中定义 |
快速总结
// ✅ 正确的 module.exports 结构
module.exports = {
// 数据
data() { return {}; },
// 计算属性
computed: {},
// 方法
methods: {},
// 生命周期钩子
mounted() {
// DOM 已就绪,可以安全操作
this.$nextTick(() => {
// 确保子组件也渲染完成
});
},
beforeDestroy() {
// 清理工作
}
};
有任何具体问题欢迎继续提问! 🚀