Vue2 进阶实战:watch、computed 与生命周期的综合应用

298 阅读4分钟

Vue2 进阶实战:watch、computed 与生命周期的综合应用

一、核心概念回顾

1. 生命周期钩子

Vue 实例从创建到销毁的完整过程,包含 8 个核心钩子:

  • 创建阶段beforeCreatecreated
  • 挂载阶段beforeMountmounted
  • 更新阶段beforeUpdateupdated
  • 销毁阶段beforeDestroydestroyed

2. computed 计算属性

  • 用途:基于 dataprops 计算衍生数据
  • 特点
    • 自动缓存,仅当依赖数据变化时重新计算
    • 必须返回值,常用于模板渲染
  • 示例
    computed: {
      fullName() {
        return `${this.firstName} ${this.lastName}`;
      }
    }
    

3. watch 监听器

  • 用途:监听 dataprops 的变化,执行副作用逻辑
  • 特点
    • 支持异步操作和复杂逻辑
    • 可配置 deep: true(深度监听对象)和 immediate: true(立即执行)
  • 示例
    watch: {
      searchQuery(newVal, oldVal) {
        this.fetchData(newVal);
      }
    }
    

二、综合应用场景:数据仪表盘

场景描述

构建一个实时数据仪表盘组件,需满足以下需求:

  1. 数据加载:在组件挂载后发起异步请求获取数据
  2. 数据校验:监听用户输入,限制数值范围(如年龄必须为正数)
  3. 动态计算:根据原始数据生成统计信息(如数据条数、平均值)
  4. 资源清理:在组件销毁前取消未完成的网络请求

示例代码

<template>
  <div class="dashboard">
    <h2>实时数据监控</h2>
    <div>
      <label>年龄:</label>
      <input v-model.number="age" @input="validateAge" />
    </div>
    <div>
      <p>数据条数:{{ dataCount }}</p>
      <p>平均值:{{ averageValue }}</p>
    </div>
  </div>
</template>
new Vue({
  el: '#app',
  data: {
    age: 25, // 用户输入的年龄
    rawData: [], // 原始数据
    requestId: null // 数据请求标识
  },
  // 计算属性:处理数据生成统计信息
  computed: {
    dataCount() {
      return this.rawData.length;
    },
    averageValue() {
      return this.rawData.length ?
        parseFloat(this.rawData.reduce((sum, num) => sum + num, 0) / this.rawData.length).toFixed(2) :
        0;
    }
  },
  // 监听器:校验年龄并限制范围
  watch: {
    age: {
      handler(newVal) {
        if (newVal < 0) {
          this.age = 0; // 强制修正非法值
        }
      },
      immediate: true // 初始化时立即执行校验
    },
    rawData: {
      handler(newData) {
        this.logDataChange(newData);
      },
      deep: true // 深度监听数组变化
    }
  },
  // 生命周期钩子
  created() {
    console.log('created: 初始化数据');
    this.requestId = this.loadData(); // 发起异步请求
  },
  mounted() {
    console.log('mounted: 渲染完成,可操作DOM');
    this.$nextTick(() => {
      document.querySelector('.dashboard').style.border = '1px solid #ccc';
    });
  },
  beforeDestroy() {
    console.log('beforeDestroy: 清理资源');
    if (this.requestId) {
      cancelRequest(this.requestId); // 取消未完成的请求
    }
  },
  methods: {
    // 模拟异步数据加载
    loadData() {
      return requestData().then(data => {
        this.rawData = data;
      }).catch(err => {
        console.error('数据加载失败:', err);
      });
    },
    // 年龄校验逻辑
    validateAge(event) {
      const value = parseInt(event.target.value);
      if (isNaN(value) || value < 0) {
        alert('年龄必须为非负整数!');
        event.target.value = 0; // 强制修正输入框值
      }
    },
    // 监听数据变化的日志记录
    logDataChange(data) {
      console.log('数据更新:', data);
    }
  }
});

三、代码执行流程解析

1. 初始化阶段(created

  • 动作:调用 loadData() 发起异步请求加载数据
  • 关键逻辑:保存请求 ID 以便后续取消
created() {
  this.requestId = this.loadData(); // 保存请求标识
}

2. 挂载阶段(mounted

  • 动作:操作 DOM 元素样式(如添加边框)
  • 注意:需在 this.$nextTick 中执行,确保 DOM 已渲染
mounted() {
  this.$nextTick(() => {
    document.querySelector('.dashboard').style.border = '1px solid #ccc';
  });
}

3. 数据监听(watch

  • age 监听
    • immediate: true:初始化时立即校验输入值
    • 强制修正非法输入(负数或非数字)
watch: {
  age: {
    handler(newVal) {
      if (newVal < 0) {
        this.age = 0;
      }
    },
    immediate: true
  }
}
  • rawData 监听
    • deep: true:监听数组内部变化(增删改)
    • 记录数据变更日志
watch: {
  rawData: {
    handler(newData) {
      this.logDataChange(newData);
    },
    deep: true
  }
}

4. 计算属性(computed

  • dataCount:直接返回数组长度,依赖 rawData
  • averageValue:计算平均值并格式化,依赖 rawData
computed: {
  dataCount() { return this.rawData.length; },
  averageValue() { return parseFloat(this.rawData.reduce((sum, num) => sum + num, 0) / this.rawData.length).toFixed(2); }
}

5. 资源清理(beforeDestroy

  • 动作:取消未完成的异步请求,防止内存泄漏
beforeDestroy() {
  if (this.requestId) { cancelRequest(this.requestId); }
}

四、常见问题与最佳实践

1. 为什么在 mounted 中操作 DOM?

  • 原因mounted 时模板已渲染完成,可安全操作真实 DOM。若在 created 中操作,DOM 尚未挂载,会导致错误。

2. computed 与 watch 的区别

特性computedwatch
触发时机依赖数据变化时自动计算数据变化时手动执行回调
返回值必须返回值(用于模板渲染)无返回值限制
性能优化自动缓存,避免重复计算需手动优化(如限制监听深度)
适用场景处理派生数据(如过滤、排序)执行副作用逻辑(如异步请求)

3. 避免在 destroyed 中使用 this

  • 问题:组件销毁后,this 指向普通对象,无法访问组件实例属性。
  • 解决方案:在 beforeDestroy 中完成所有清理逻辑。

4. watch 的性能优化

  • 深度监听:仅在必要时设置 deep: true,避免性能损耗。
  • 立即执行:谨慎使用 immediate: true,防止初始化时重复触发。

五、总结与建议

1. 综合使用原则

  • 生命周期:在 created/mounted 中初始化数据,在 beforeDestroy 中清理资源。
  • computed:处理需要缓存的派生数据,避免重复计算。
  • watch:监听数据变化并执行副作用逻辑(如异步请求、校验)。

2. 调试技巧

  • 日志输出:在每个钩子中添加 console.log,观察执行顺序。
  • 断点调试:使用浏览器开发者工具设置断点,检查数据流和DOM状态。

3. 进阶扩展

  • 结合路由钩子:在 mounted 中同步路由参数和组件数据。
  • 自定义混入:通过全局混入(Vue.mixin)复用生命周期逻辑。

通过本文的示例和分析,您可以掌握 Vue2 中 watch、computed 和生命周期的核心用法,在实际项目中实现高效、可维护的组件开发。