Vue 2 + CommonJS 写法开发教程

复制代码
❌ 错误写法
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 组件选项中 确保 mountedmodule.exports的对象内部
DOM 操作无效 DOM 还未渲染 使用 this.$nextTick()或放在 mounted
内存泄漏 未清理事件监听/定时器 beforeDestroy中清理
this指向错误 箭头函数问题 使用普通函数或在 methods中定义

快速总结

复制代码
// ✅ 正确的 module.exports 结构
module.exports = {
  // 数据
  data() { return {}; },
  
  // 计算属性
  computed: {},
  
  // 方法
  methods: {},
  
  // 生命周期钩子
  mounted() {
    // DOM 已就绪,可以安全操作
    this.$nextTick(() => {
      // 确保子组件也渲染完成
    });
  },
  
  beforeDestroy() {
    // 清理工作
  }
};

有任何具体问题欢迎继续提问! 🚀

相关推荐
minstbe2 小时前
IC设计私有化AI助手实战:基于Docker+OpenCode+Ollama的数字前端综合增强方案(基础版)
前端·人工智能·docker
qq_246100052 小时前
CSDN risk probe 1773588273
开发语言·javascript·ecmascript
ByteCraze2 小时前
Vue 递归组件实战:手写一个文件/文件夹树形组件
javascript·vue.js·ecmascript
前端Hardy3 小时前
前端如何防止用户重复提交表单?4 种可靠方案(附防坑指南)
前端·javascript·面试
前端Hardy3 小时前
用户真的关掉页面了吗?前端精准检测页面卸载的 4 种方法(附避坑指南)
前端·javascript·面试
yangyanping201083 小时前
Vue入门到精通七之关键字const
前端·javascript·vue.js
姝然_95273 小时前
Jetpack Compose 绘制流程与自定义布局
前端
lxh01133 小时前
重复的DNA序列
开发语言·javascript·ecmascript