Vue框架之钩子函数详解

Vue框架之生命周期主要钩子函数详解

Vue的生命周期是指组件从创建到销毁的整个过程,而生命周期钩子函数则是在这个过程中特定时间点自动执行的函数。掌握这些钩子函数,能让我们在合适的时机执行特定操作(如数据请求、DOM操作、资源清理等),是Vue开发的核心基础。

一、Vue生命周期的整体流程

Vue组件的生命周期可分为4个阶段,每个阶段包含若干钩子函数,整体流程如下:

  1. 创建阶段:组件实例从初始化到挂载前的过程
  2. 挂载阶段:组件实例挂载到DOM的过程
  3. 更新阶段:组件数据变化导致重新渲染的过程
  4. 销毁阶段:组件实例从DOM中移除并清理资源的过程

二、创建阶段:初始化组件实例

创建阶段是组件实例从无到有的过程,主要完成数据观测(响应式处理)、事件初始化等工作,此时尚未涉及DOM操作。

2.1 beforeCreate:实例创建前

  • 执行时机 :Vue实例初始化后(new Vue()之后),数据观测(dataprops)和事件机制初始化前调用。
  • 特点
    • 无法访问datapropsmethods中的数据和方法(此时尚未初始化)。
    • 不能进行DOM操作(DOM尚未生成)。
  • 适用场景:极少使用,可用于初始化非响应式数据(如临时变量)。
javascript 复制代码
new Vue({
  data() {
    return { message: 'Hello' };
  },
  beforeCreate() {
    console.log('beforeCreate:', this.message); // undefined(无法访问data)
    console.log('methods:', this.getMsg); // undefined(无法访问methods)
  },
  methods: {
    getMsg() { return this.message; }
  }
});

2.2 created:实例创建后

  • 执行时机 :Vue实例初始化完成后调用,此时已完成dataprops的响应式处理和methods的绑定,但尚未开始DOM编译(模板未挂载到DOM)。
  • 特点
    • 可访问datapropsmethods中的数据和方法。
    • 仍无法进行DOM操作($el属性不存在,DOM未生成)。
  • 适用场景
    • 发起初始化数据请求(如获取列表数据)。
    • 初始化数据(如对data中的数据进行预处理)。
    • 绑定自定义事件。
javascript 复制代码
new Vue({
  el: '#app',
  data() {
    return { list: [] };
  },
  created() {
    console.log('created:', this.list); // [](可访问data)
    console.log('$el:', this.$el); // undefined(DOM未挂载)
    
    // 示例:发起数据请求
    axios.get('/api/list')
      .then(response => {
        this.list = response.data; // 数据响应后更新list(响应式)
      });
  }
});

三、挂载阶段:组件与DOM结合

挂载阶段是组件实例与DOM关联的过程,核心是将编译后的模板挂载到页面中,此时开始具备DOM操作能力。

3.1 beforeMount:挂载前

  • 执行时机:模板编译(解析指令、插值表达式等)完成后,DOM挂载到页面之前调用。
  • 特点
    • 已完成模板编译,生成虚拟DOM(但未渲染到页面)。
    • $el属性存在(指向即将挂载的DOM元素),但内容仍为原始模板(未替换数据)。
    • 仍无法操作DOM(数据未渲染,操作无意义)。
  • 适用场景:极少使用,可用于获取模板编译前的DOM结构。
javascript 复制代码
new Vue({
  el: '#app',
  template: '<div>{{ message }}</div>',
  data() { return { message: '挂载前' }; },
  beforeMount() {
    console.log('beforeMount $el:', this.$el); // <div>{{ message }}</div>(未替换)
    console.log('页面内容:', document.getElementById('app').innerHTML); // 原始模板
  }
});

3.2 mounted:挂载后

  • 执行时机:模板挂载到DOM后调用,此时页面已显示渲染后的内容。
  • 特点
    • DOM已完全渲染,可通过$el或原生DOM API(如document.getElementById)操作DOM。
    • 子组件的mounted可能在父组件的mounted之后执行(因渲染顺序)。
  • 适用场景
    • 执行DOM操作(如初始化第三方UI插件:图表、地图等,需依赖DOM元素)。
    • 监听DOM事件(如滚动、resize)。
    • 若数据请求依赖DOM尺寸(如根据容器宽度请求不同数据),可在此发起请求。
javascript 复制代码
new Vue({
  el: '#app',
  data() { return { width: 0 }; },
  mounted() {
    // 获取DOM元素宽度
    this.width = this.$el.offsetWidth;
    console.log('挂载后宽度:', this.width);
    
    // 初始化第三方图表插件(假设页面有<div id="chart"></div>)
    this.chart = new Chart(document.getElementById('chart'), {
      type: 'line',
      data: { labels: ['1月', '2月'], datasets: [{ data: [10, 20] }] }
    });
    
    // 监听滚动事件
    window.addEventListener('scroll', this.handleScroll);
  },
  methods: {
    handleScroll() { /* 处理滚动逻辑 */ }
  }
});

四、更新阶段:数据变化触发重渲染

当组件的dataprops发生变化时,会进入更新阶段,触发重新渲染,此阶段的钩子函数用于监控或干预更新过程。

4.1 beforeUpdate:更新前

  • 执行时机:数据发生变化后,虚拟DOM重新渲染前调用。
  • 特点
    • 此时data中的数据已更新,但DOM尚未重新渲染(页面显示旧数据)。
    • 可获取更新前的DOM状态。
  • 适用场景:获取更新前的DOM信息(如滚动位置、输入框光标位置),用于更新后恢复状态。
javascript 复制代码
new Vue({
  el: '#app',
  data() { return { count: 0 }; },
  template: '<div>{{ count }} <button @click="count++">+1</button></div>',
  beforeUpdate() {
    console.log('更新前data:', this.count); // 新值(如1)
    console.log('更新前DOM:', this.$el.textContent); // 旧值(如0)
  }
});

4.2 updated:更新后

  • 执行时机:虚拟DOM重新渲染并更新到页面后调用,此时页面显示最新数据。
  • 特点
    • data和DOM均已更新,可获取最新的DOM状态。
    • 若在updated中修改data,会再次触发更新(可能导致无限循环,需避免)。
  • 适用场景
    • 基于最新DOM状态执行操作(如根据新内容调整元素样式)。
    • 同步第三方插件数据(如图表数据更新后,重新绘制图表)。
javascript 复制代码
new Vue({
  el: '#app',
  data() { return { data: [10, 20] }; },
  template: '<div>{{ data }}</div>',
  updated() {
    console.log('更新后DOM:', this.$el.textContent); // 显示最新data
    // 若图表数据依赖this.data,更新后重新绘制
    if (this.chart) {
      this.chart.data.datasets[0].data = this.data;
      this.chart.update();
    }
  }
});

五、销毁阶段:组件实例的清理

当组件被销毁(如v-if="false"移除组件、路由切换)时,进入销毁阶段,此阶段的钩子函数用于清理资源,避免内存泄漏。

5.1 beforeDestroy:销毁前

  • 执行时机:组件实例销毁前调用,此时组件仍处于正常工作状态。
  • 特点
    • 可访问datamethods、DOM等所有资源。
    • 子组件的beforeDestroy会在父组件的beforeDestroy前执行。
  • 适用场景
    • 清理定时器、事件监听器(避免组件销毁后仍执行)。
    • 取消未完成的请求(避免资源浪费)。
    • 解绑自定义事件。
javascript 复制代码
new Vue({
  el: '#app',
  data() {
    return { timer: null };
  },
  mounted() {
    // 启动定时器
    this.timer = setInterval(() => {
      console.log('定时器执行中...');
    }, 1000);
    
    // 绑定事件
    window.addEventListener('resize', this.handleResize);
  },
  beforeDestroy() {
    // 清理定时器
    clearInterval(this.timer);
    
    // 移除事件监听
    window.removeEventListener('resize', this.handleResize);
    
    // 取消未完成的请求(假设使用axios)
    if (this.source) {
      this.source.cancel('组件销毁,取消请求');
    }
  },
  methods: {
    handleResize() { /* 处理窗口大小变化 */ }
  }
});

5.2 destroyed:销毁后

  • 执行时机:组件实例销毁后调用,此时组件的所有资源已被释放。
  • 特点
    • 组件的响应式数据、事件监听、子组件等均已被销毁。
    • 仍可访问$el,但DOM可能已被移除(取决于销毁方式)。
  • 适用场景:极少使用,可用于最终的资源清理或日志记录。
javascript 复制代码
new Vue({
  destroyed() {
    console.log('组件已销毁');
    // 记录销毁日志
    console.log('组件销毁时间:', new Date().toLocaleString());
  }
});

六、特殊场景的钩子函数

除上述核心钩子外,Vue还提供了针对特殊场景的钩子函数:

6.1 keep-alive 相关钩子

keep-alive用于缓存组件(避免频繁创建/销毁),搭配两个专属钩子:

  • activated:缓存的组件被激活(显示)时调用。
  • deactivated:缓存的组件被停用(隐藏)时调用。
html 复制代码
<!-- 缓存组件 -->
<keep-alive>
  <component :is="currentComponent"></component>
</keep-alive>

<script>
new Vue({
  data() { return { currentComponent: 'ComponentA' }; },
  components: {
    ComponentA: {
      template: '<div>组件A</div>',
      activated() {
        console.log('组件A被激活(从缓存中取出)');
        // 可在此恢复状态(如刷新数据)
      },
      deactivated() {
        console.log('组件A被停用(存入缓存)');
        // 可在此暂停操作(如暂停视频播放)
      }
    }
  }
});
</script>

6.2 错误捕获钩子:errorCaptured

用于捕获子组件抛出的错误(Vue 2.5+),返回false可阻止错误向上传播:

javascript 复制代码
new Vue({
  errorCaptured(err, vm, info) {
    console.error('捕获子组件错误:', err, '组件:', vm, '信息:', info);
    // 返回false阻止错误冒泡到控制台
    return false;
  }
});

七、生命周期钩子的执行顺序与实战示例

7.1 单组件执行顺序

javascript 复制代码
new Vue({
  // 创建阶段
  beforeCreate() { console.log('1. beforeCreate'); },
  created() { console.log('2. created'); },
  // 挂载阶段
  beforeMount() { console.log('3. beforeMount'); },
  mounted() { console.log('4. mounted'); },
  // 更新阶段(触发条件:修改data)
  beforeUpdate() { console.log('5. beforeUpdate'); },
  updated() { console.log('6. updated'); },
  // 销毁阶段(触发条件:调用$destroy()或v-if移除)
  beforeDestroy() { console.log('7. beforeDestroy'); },
  destroyed() { console.log('8. destroyed'); }
});

执行结果

初始化时:1→2→3→4

修改数据时:5→6

销毁时:7→8

7.2 父子组件执行顺序

父组件Parent和子组件Child的钩子执行顺序:

  1. beforeCreate → 父created → 父beforeMount
  2. beforeCreate → 子created → 子beforeMount → 子mounted
  3. mounted
  4. (更新时)父beforeUpdate → 子beforeUpdate → 子updated → 父updated
  5. (销毁时)父beforeDestroy → 子beforeDestroy → 子destroyed → 父destroyed

结论:父组件等待子组件完成后,才会完成自身对应的阶段。

八、常见问题与避坑指南

8.1 避免在updated中修改数据

javascript 复制代码
// 错误示例:导致无限循环
updated() {
  this.count++; // 修改data触发更新,再次调用updated,循环往复
}

解决方案 :若需根据更新后的数据调整状态,可使用$nextTick或条件判断限制执行次数。

8.2 子组件mounted晚于父组件mounted

父组件若需等待子组件挂载完成后执行操作(如获取子组件DOM),需使用$nextTick或在子组件中通过事件通知父组件:

javascript 复制代码
// 子组件
export default {
  mounted() {
    this.$emit('mounted'); // 通知父组件已挂载
  }
};

// 父组件
<child-component @mounted="handleChildMounted"></child-component>
methods: {
  handleChildMounted() {
    console.log('子组件已挂载');
  }
}

8.3 清理资源是重中之重

组件销毁时若未清理定时器、事件监听等,会导致内存泄漏(页面关闭前资源一直占用):

javascript 复制代码
// 错误:未清理定时器
mounted() {
  setInterval(() => { /* 操作 */ }, 1000); // 组件销毁后仍会执行
}

// 正确:在beforeDestroy中清理
beforeDestroy() {
  clearInterval(this.timer);
}

九、Vue 3中的生命周期变化

Vue 3的Composition API中,生命周期钩子的使用方式有所调整,但核心逻辑一致:

  • setup():替代beforeCreatecreated(在两者之间执行)。
  • onBeforeMount:对应beforeMount
  • onMounted:对应mounted
  • onBeforeUpdate:对应beforeUpdate
  • onUpdated:对应updated
  • onBeforeUnmount:对应beforeDestroy
  • onUnmounted:对应destroyed
javascript 复制代码
// Vue 3 Composition API示例
import { onMounted, onBeforeUnmount } from 'vue';

export default {
  setup() {
    let timer;
    
    onMounted(() => {
      timer = setInterval(() => { /* 操作 */ }, 1000);
    });
    
    onBeforeUnmount(() => {
      clearInterval(timer); // 清理资源
    });
  }
};

总结

  1. 精准控制流程 :在合适的阶段执行合适的操作(如created请求数据、mounted操作DOM)。
  2. 避免常见问题:如更新阶段的死循环、销毁阶段的内存泄漏。
  3. 优化组件性能 :合理利用keep-alive和缓存钩子,减少不必要的创建/销毁。
    若这篇内容帮到你,动动手指支持下!关注不迷路,干货持续输出!

ヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノ

相关推荐
海天胜景1 小时前
vue3 el-table动态表头
javascript·vue.js·elementui
鱼樱前端5 小时前
2025前端人一文看懂 Broadcast Channel API 通信指南
前端·vue.js
鱼樱前端6 小时前
2025前端人一文看懂 window.postMessage 通信
前端·vue.js
浮桥8 小时前
vue3实现pdf文件预览 - vue-pdf-embed
前端·vue.js·pdf
四季豆豆豆9 小时前
博客项目 laravel vue mysql 第四章 分类功能
vue.js·mysql·laravel
拾光拾趣录10 小时前
Vue中v-if与v-for同元素使用的陷阱
前端·vue.js
江城开朗的豌豆13 小时前
退出登录后头像还在?这个缓存问题坑过多少前端!
前端·javascript·vue.js
江城开朗的豌豆14 小时前
Vue的'读心术':它怎么知道数据偷偷变了?
前端·javascript·vue.js
江城开朗的豌豆14 小时前
手把手教你造一个自己的v-model:原来双向绑定这么简单!
前端·javascript·vue.js