Skip to content

生命周期

Vue组件的生命周期是指组件从创建到销毁的整个过程,Vue框架在这个过程中提供了一系列的钩子函数,允许开发者在不同阶段执行自定义的逻辑。本文将详细介绍Vue 2和Vue 3的生命周期钩子及其使用场景。

Vue 2的生命周期

1. 生命周期图示

Vue 2的生命周期可以分为以下几个阶段:

  1. 初始化阶段:组件实例被创建,初始化数据和事件。
  2. 挂载阶段:组件被挂载到DOM上,开始渲染。
  3. 更新阶段:组件数据发生变化,重新渲染。
  4. 销毁阶段:组件被销毁,清理资源。

2. 生命周期钩子

2.1 初始化阶段

  • beforeCreate:在实例初始化之后,数据观测(data observer)和event/watcher事件配置之前被调用。
  • created:在实例创建完成后被调用。此时,实例已完成以下配置:数据观测(data observer),属性和方法的运算,watch/event事件回调。但挂载阶段还未开始,$el属性目前不可见。

2.2 挂载阶段

  • beforeMount:在挂载开始之前被调用:相关的render函数首次被调用。
  • mounted:el被新创建的vm.$el替换,并挂载到实例上去之后调用该钩子。如果root实例挂载了一个文档内元素,当mounted被调用时vm.$el也在文档内。

2.3 更新阶段

  • beforeUpdate:数据更新时调用,发生在虚拟DOM打补丁之前。这里适合在更新之前访问现有的DOM,比如手动移除已添加的事件监听器。
  • updated:由于数据更改导致的虚拟DOM重新渲染和打补丁,在这之后会调用该钩子。当这个钩子被调用时,组件DOM已经更新,所以你现在可以执行依赖于DOM的操作。

2.4 销毁阶段

  • beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。
  • destroyed:Vue实例销毁后调用。调用后,Vue实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。

2.5 其他钩子

  • activated:keep-alive组件激活时调用。
  • deactivated:keep-alive组件停用时调用。
  • errorCaptured:当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回false以阻止该错误继续向上传播。

3. 生命周期钩子的使用

javascript
// Vue 2组件生命周期示例
new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  },
  beforeCreate() {
    console.log('beforeCreate: 实例初始化之前');
    console.log('data:', this.message); // undefined
    console.log('el:', this.$el); // undefined
  },
  created() {
    console.log('created: 实例创建完成');
    console.log('data:', this.message); // Hello Vue!
    console.log('el:', this.$el); // undefined
  },
  beforeMount() {
    console.log('beforeMount: 挂载开始之前');
    console.log('el:', this.$el); // <div id="app"></div>
  },
  mounted() {
    console.log('mounted: 挂载完成');
    console.log('el:', this.$el); // <div id="app">Hello Vue!</div>
  },
  beforeUpdate() {
    console.log('beforeUpdate: 数据更新之前');
    console.log('message:', this.message); // 新值
  },
  updated() {
    console.log('updated: 数据更新完成');
    console.log('message:', this.message); // 新值
  },
  beforeDestroy() {
    console.log('beforeDestroy: 实例销毁之前');
  },
  destroyed() {
    console.log('destroyed: 实例销毁完成');
  }
});

4. 生命周期钩子的执行顺序

对于父组件和子组件,生命周期钩子的执行顺序如下:

  1. 父组件:beforeCreate
  2. 父组件:created
  3. 父组件:beforeMount
  4. 子组件:beforeCreate
  5. 子组件:created
  6. 子组件:beforeMount
  7. 子组件:mounted
  8. 父组件:mounted
  9. 父组件:beforeUpdate
  10. 子组件:beforeUpdate
  11. 子组件:updated
  12. 父组件:updated
  13. 父组件:beforeDestroy
  14. 子组件:beforeDestroy
  15. 子组件:destroyed
  16. 父组件:destroyed

Vue 3的生命周期

1. 生命周期图示

Vue 3的生命周期与Vue 2类似,但使用了组合式API,钩子函数的名称和使用方式有所不同。

2. 生命周期钩子

Vue 3的生命周期钩子可以通过 on 前缀的函数来使用,这些函数需要从 vue 中导入。

2.1 初始化阶段

  • setup:组合式API的入口点,在组件创建之前执行,替代了Vue 2的 beforeCreatecreated 钩子。

2.2 挂载阶段

  • onBeforeMount:在挂载开始之前被调用,对应Vue 2的 beforeMount
  • onMounted:在组件挂载完成后被调用,对应Vue 2的 mounted

2.3 更新阶段

  • onBeforeUpdate:在数据更新之前被调用,对应Vue 2的 beforeUpdate
  • onUpdated:在数据更新完成后被调用,对应Vue 2的 updated

2.4 销毁阶段

  • onBeforeUnmount:在组件卸载之前被调用,对应Vue 2的 beforeDestroy
  • onUnmounted:在组件卸载完成后被调用,对应Vue 2的 destroyed

2.5 其他钩子

  • onActivated:keep-alive组件激活时调用,对应Vue 2的 activated
  • onDeactivated:keep-alive组件停用时调用,对应Vue 2的 deactivated
  • onErrorCaptured:当捕获一个来自子孙组件的错误时被调用,对应Vue 2的 errorCaptured
  • onRenderTracked:在响应式依赖被收集时调用,仅在开发模式下使用。
  • onRenderTriggered:在响应式依赖被触发时调用,仅在开发模式下使用。

3. 生命周期钩子的使用

javascript
// Vue 3组件生命周期示例
import { ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue';

export default {
  setup() {
    const message = ref('Hello Vue 3!');
    
    console.log('setup: 组件初始化');
    
    onBeforeMount(() => {
      console.log('onBeforeMount: 挂载开始之前');
    });
    
    onMounted(() => {
      console.log('onMounted: 挂载完成');
    });
    
    onBeforeUpdate(() => {
      console.log('onBeforeUpdate: 数据更新之前');
    });
    
    onUpdated(() => {
      console.log('onUpdated: 数据更新完成');
    });
    
    onBeforeUnmount(() => {
      console.log('onBeforeUnmount: 组件卸载之前');
    });
    
    onUnmounted(() => {
      console.log('onUnmounted: 组件卸载完成');
    });
    
    return {
      message
    };
  }
};

4. 生命周期钩子的执行顺序

Vue 3的生命周期钩子执行顺序与Vue 2类似,但由于使用了组合式API,setup 函数会在所有生命周期钩子之前执行。

生命周期钩子的使用场景

1. 初始化阶段

  • beforeCreate/created (Vue 2) / setup (Vue 3):
    • 初始化数据和事件。
    • 发起API请求,获取初始数据。
    • 配置全局事件监听器。

2. 挂载阶段

  • beforeMount (Vue 2) / onBeforeMount (Vue 3):

    • 访问DOM元素,进行初始化操作。
    • 配置第三方库。
  • mounted (Vue 2) / onMounted (Vue 3):

    • 执行DOM操作,如获取元素尺寸、添加事件监听器。
    • 启动定时器或WebSocket连接。
    • 发起依赖DOM的API请求。

3. 更新阶段

  • beforeUpdate (Vue 2) / onBeforeUpdate (Vue 3):

    • 访问更新前的DOM状态。
    • 清理不需要的事件监听器。
  • updated (Vue 2) / onUpdated (Vue 3):

    • 执行依赖DOM更新的操作,如滚动到指定位置。
    • 重新计算DOM元素的尺寸。

4. 销毁阶段

  • beforeDestroy (Vue 2) / onBeforeUnmount (Vue 3):

    • 清理定时器和WebSocket连接。
    • 移除事件监听器。
    • 取消未完成的API请求。
  • destroyed (Vue 2) / onUnmounted (Vue 3):

    • 执行最终的清理操作。

5. 其他场景

  • activated (Vue 2) / onActivated (Vue 3):

    • 当keep-alive组件被激活时,执行恢复状态的操作。
  • deactivated (Vue 2) / onDeactivated (Vue 3):

    • 当keep-alive组件被停用时,执行暂停状态的操作。
  • errorCaptured (Vue 2) / onErrorCaptured (Vue 3):

    • 捕获和处理组件树中的错误。
    • 记录错误信息。

生命周期钩子的最佳实践

1. 合理使用生命周期钩子

  • 初始化数据:在 created (Vue 2) 或 setup (Vue 3) 中初始化数据和发起API请求。
  • DOM操作:在 mounted (Vue 2) 或 onMounted (Vue 3) 中执行DOM操作。
  • 清理资源:在 beforeDestroy (Vue 2) 或 onBeforeUnmount (Vue 3) 中清理资源。

2. 避免在生命周期钩子中执行耗时操作

  • 初始化阶段:避免在 createdsetup 中执行耗时操作,可能会阻塞组件的渲染。
  • 更新阶段:避免在 updated 中执行耗时操作,可能会影响用户体验。

3. 使用异步操作

  • API请求:使用 async/await 或 Promise 处理异步API请求。
  • DOM操作:对于依赖DOM的操作,确保在 mountedonMounted 中执行。

4. 避免在生命周期钩子中修改数据

  • 更新阶段:避免在 updated 中修改数据,可能会导致无限循环更新。
  • 销毁阶段:避免在 destroyedonUnmounted 中修改数据,此时组件已经被销毁。

生命周期钩子的实际应用

1. 数据获取

javascript
// Vue 2
new Vue({
  data() {
    return {
      users: []
    };
  },
  created() {
    // 发起API请求获取数据
    fetch('https://api.example.com/users')
      .then(response => response.json())
      .then(data => {
        this.users = data;
      })
      .catch(error => {
        console.error('Error fetching users:', error);
      });
  }
});

// Vue 3
import { ref, onMounted } from 'vue';

export default {
  setup() {
    const users = ref([]);
    
    onMounted(async () => {
      try {
        const response = await fetch('https://api.example.com/users');
        const data = await response.json();
        users.value = data;
      } catch (error) {
        console.error('Error fetching users:', error);
      }
    });
    
    return {
      users
    };
  }
};

2. DOM操作

javascript
// Vue 2
new Vue({
  mounted() {
    // 获取元素尺寸
    this.$nextTick(() => {
      const width = this.$el.offsetWidth;
      const height = this.$el.offsetHeight;
      console.log(`Element size: ${width}x${height}`);
    });
  }
});

// Vue 3
import { ref, onMounted } from 'vue';

export default {
  setup() {
    const elementRef = ref(null);
    
    onMounted(() => {
      // 获取元素尺寸
      if (elementRef.value) {
        const width = elementRef.value.offsetWidth;
        const height = elementRef.value.offsetHeight;
        console.log(`Element size: ${width}x${height}`);
      }
    });
    
    return {
      elementRef
    };
  }
};

3. 定时器和事件监听器

javascript
// Vue 2
new Vue({
  data() {
    return {
      count: 0,
      timer: null
    };
  },
  mounted() {
    // 启动定时器
    this.timer = setInterval(() => {
      this.count++;
    }, 1000);
    
    // 添加事件监听器
    window.addEventListener('resize', this.handleResize);
  },
  beforeDestroy() {
    // 清理定时器
    if (this.timer) {
      clearInterval(this.timer);
    }
    
    // 移除事件监听器
    window.removeEventListener('resize', this.handleResize);
  },
  methods: {
    handleResize() {
      console.log('Window resized');
    }
  }
});

// Vue 3
import { ref, onMounted, onBeforeUnmount } from 'vue';

export default {
  setup() {
    const count = ref(0);
    let timer = null;
    
    const handleResize = () => {
      console.log('Window resized');
    };
    
    onMounted(() => {
      // 启动定时器
      timer = setInterval(() => {
        count.value++;
      }, 1000);
      
      // 添加事件监听器
      window.addEventListener('resize', handleResize);
    });
    
    onBeforeUnmount(() => {
      // 清理定时器
      if (timer) {
        clearInterval(timer);
      }
      
      // 移除事件监听器
      window.removeEventListener('resize', handleResize);
    });
    
    return {
      count
    };
  }
};

4. 第三方库集成

javascript
// Vue 2
new Vue({
  mounted() {
    // 初始化第三方库
    this.chart = new Chart(this.$refs.canvas, {
      type: 'bar',
      data: {
        labels: ['Red', 'Blue', 'Yellow'],
        datasets: [{
          label: 'Votes',
          data: [12, 19, 3]
        }]
      }
    });
  },
  beforeDestroy() {
    // 销毁第三方库实例
    if (this.chart) {
      this.chart.destroy();
    }
  }
});

// Vue 3
import { ref, onMounted, onBeforeUnmount } from 'vue';

export default {
  setup() {
    const canvasRef = ref(null);
    let chart = null;
    
    onMounted(() => {
      // 初始化第三方库
      chart = new Chart(canvasRef.value, {
        type: 'bar',
        data: {
          labels: ['Red', 'Blue', 'Yellow'],
          datasets: [{
            label: 'Votes',
            data: [12, 19, 3]
          }]
        }
      });
    });
    
    onBeforeUnmount(() => {
      // 销毁第三方库实例
      if (chart) {
        chart.destroy();
      }
    });
    
    return {
      canvasRef
    };
  }
};

面试常见问题

1. Vue 2和Vue 3的生命周期有什么区别?

Vue 2和Vue 3的生命周期主要区别如下:

Vue 2Vue 3说明
beforeCreatesetupVue 3中setup替代了beforeCreate和created
createdsetupVue 3中setup替代了beforeCreate和created
beforeMountonBeforeMount名称变化,功能类似
mountedonMounted名称变化,功能类似
beforeUpdateonBeforeUpdate名称变化,功能类似
updatedonUpdated名称变化,功能类似
beforeDestroyonBeforeUnmount名称变化,功能类似
destroyedonUnmounted名称变化,功能类似
activatedonActivated名称变化,功能类似
deactivatedonDeactivated名称变化,功能类似
errorCapturedonErrorCaptured名称变化,功能类似
-onRenderTrackedVue 3新增,开发模式下使用
-onRenderTriggeredVue 3新增,开发模式下使用

2. 生命周期钩子的执行顺序是什么?

Vue 2的生命周期钩子执行顺序:

  1. beforeCreate
  2. created
  3. beforeMount
  4. mounted
  5. beforeUpdate
  6. updated
  7. beforeDestroy
  8. destroyed

Vue 3的生命周期钩子执行顺序:

  1. setup
  2. onBeforeMount
  3. onMounted
  4. onBeforeUpdate
  5. onUpdated
  6. onBeforeUnmount
  7. onUnmounted

3. 哪些生命周期钩子适合发起API请求?

  • Vue 2created 钩子适合发起API请求,此时实例已经创建完成,可以访问数据和方法。
  • Vue 3onMounted 钩子适合发起API请求,或者在 setup 函数中使用异步操作。

4. 哪些生命周期钩子适合执行DOM操作?

  • Vue 2mounted 钩子适合执行DOM操作,此时组件已经挂载到DOM上,可以访问DOM元素。
  • Vue 3onMounted 钩子适合执行DOM操作,此时组件已经挂载到DOM上,可以访问DOM元素。

5. 哪些生命周期钩子适合清理资源?

  • Vue 2beforeDestroy 钩子适合清理资源,此时实例仍然可用,可以访问数据和方法。
  • Vue 3onBeforeUnmount 钩子适合清理资源,此时组件仍然可用,可以访问数据和方法。

6. 为什么在created钩子中可以访问数据但不能访问DOM?

created 钩子中,Vue实例已经创建完成,数据观测和事件配置已经完成,所以可以访问数据和方法。但此时挂载阶段还未开始,$el 属性还未被创建,所以不能访问DOM。

7. 为什么在mounted钩子中可以访问DOM?

mounted 钩子中,组件已经被挂载到DOM上,$el 属性已经被创建并指向挂载的DOM元素,所以可以访问DOM。

8. 如何在Vue 3中使用生命周期钩子?

在Vue 3中,生命周期钩子需要从 vue 中导入,然后在 setup 函数中使用:

javascript
import { onMounted, onBeforeUnmount } from 'vue';

export default {
  setup() {
    onMounted(() => {
      console.log('Component mounted');
    });
    
    onBeforeUnmount(() => {
      console.log('Component before unmount');
    });
  }
};

9. 什么是组合式API的setup函数?它在生命周期中的位置是什么?

setup 函数是Vue 3组合式API的入口点,它在组件创建之前执行,替代了Vue 2的 beforeCreatecreated 钩子。在 setup 函数中,你可以:

  • 初始化响应式数据。
  • 定义方法和计算属性。
  • 注册生命周期钩子。
  • 返回需要暴露给模板的变量和方法。

10. 如何避免生命周期钩子中的内存泄漏?

避免生命周期钩子中的内存泄漏的方法:

  • 清理定时器:在 beforeDestroy (Vue 2) 或 onBeforeUnmount (Vue 3) 中使用 clearIntervalclearTimeout 清理定时器。
  • 移除事件监听器:在 beforeDestroy (Vue 2) 或 onBeforeUnmount (Vue 3) 中使用 removeEventListener 移除事件监听器。
  • 取消API请求:在 beforeDestroy (Vue 2) 或 onBeforeUnmount (Vue 3) 中取消未完成的API请求。
  • 销毁第三方库实例:在 beforeDestroy (Vue 2) 或 onBeforeUnmount (Vue 3) 中销毁第三方库实例。

总结

Vue的生命周期钩子是Vue框架提供的强大功能,允许开发者在组件的不同阶段执行自定义的逻辑。通过理解和正确使用生命周期钩子,开发者可以更好地控制组件的行为,提高应用的性能和可靠性。

Vue 2和Vue 3的生命周期钩子有所不同,Vue 3使用了组合式API,钩子函数的名称和使用方式发生了变化,但核心功能类似。开发者需要根据使用的Vue版本选择合适的生命周期钩子。

在实际开发中,开发者应该根据具体的使用场景选择合适的生命周期钩子,并遵循最佳实践,避免常见的错误,如在生命周期钩子中执行耗时操作、修改数据导致无限循环等。

通过合理使用生命周期钩子,开发者可以构建出更加健壮、高效的Vue应用。

好好学习,天天向上