MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Vue 2与Vue 3 生命周期钩子的变化与迁移指南

2024-01-167.8k 阅读

Vue 生命周期钩子函数概述

在Vue开发中,生命周期钩子函数是非常重要的概念。它们允许开发者在Vue实例从创建到销毁的不同阶段执行特定的代码逻辑。这些钩子函数为我们提供了对Vue实例在各个阶段的控制权,无论是数据初始化、DOM操作,还是资源清理等,都可以借助生命周期钩子来实现。

在Vue 2中,我们已经熟悉了一系列生命周期钩子,如 beforeCreatecreatedbeforeMountmountedbeforeUpdateupdatedbeforeDestroydestroyed 等。Vue 3在保持部分钩子函数名称和功能一致性的同时,也对生命周期钩子进行了一些改动,以适应新的Composition API和更好的代码组织。

Vue 2生命周期钩子函数详解

  1. beforeCreate:在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。此时,实例的 datamethods 等属性都还未被初始化,所以在这个钩子函数中无法访问到这些属性。
new Vue({
  data() {
    return {
      message: 'Hello'
    }
  },
  beforeCreate() {
    console.log(this.message); // undefined
  }
});
  1. created:实例已经创建完成,此时可以访问 datamethods 等属性。通常在这个钩子函数中进行数据的初始化、AJAX请求等操作。
new Vue({
  data() {
    return {
      message: 'Hello',
      user: null
    }
  },
  created() {
    // 模拟AJAX请求获取用户数据
    setTimeout(() => {
      this.user = { name: 'John' };
    }, 1000);
  }
});
  1. beforeMount:在挂载开始之前被调用,此时 $el 已经被创建,但是还没有挂载到DOM中,模板也还没有被渲染。可以在这个钩子函数中对即将挂载的DOM进行一些最后的修改。
<template>
  <div id="app">{{ message }}</div>
</template>
<script>
export default {
  data() {
    return {
      message: 'Hello'
    }
  },
  beforeMount() {
    console.log(this.$el); // <div id="app"></div>
  }
}
</script>
  1. mounted:实例被挂载后调用,此时 $el 已经挂载到DOM中,模板也已经渲染完成。可以在这个钩子函数中进行DOM操作、初始化第三方插件等。
<template>
  <div id="app">{{ message }}</div>
</template>
<script>
export default {
  data() {
    return {
      message: 'Hello'
    }
  },
  mounted() {
    console.log(this.$el.textContent); // Hello
    // 初始化第三方插件,例如Chart.js
    const ctx = this.$el.querySelector('canvas');
    new Chart(ctx, {
      type: 'bar',
      data: {
        labels: ['Red', 'Blue', 'Yellow'],
        datasets: [{
          label: '# of Votes',
          data: [12, 19, 3],
          backgroundColor: [
            'rgba(255, 99, 132, 0.2)',
            'rgba(54, 162, 235, 0.2)',
            'rgba(255, 206, 86, 0.2)'
          ],
          borderColor: [
            'rgba(255, 99, 132, 1)',
            'rgba(54, 162, 235, 1)',
            'rgba(255, 206, 86, 1)'
          ],
          borderWidth: 1
        }]
      },
      options: {
        scales: {
          yAxes: [{
            ticks: {
              beginAtZero: true
            }
          }]
        }
      }
    });
  }
}
</script>
  1. beforeUpdate:数据更新时调用,发生在虚拟DOM重新渲染和打补丁之前。可以在这个钩子函数中对即将更新的数据进行一些预处理操作。
<template>
  <div id="app">
    <input v-model="message">
    <p>{{ message }}</p>
  </div>
</template>
<script>
export default {
  data() {
    return {
      message: 'Hello'
    }
  },
  beforeUpdate() {
    console.log('数据即将更新,当前值:', this.message);
  }
}
</script>
  1. updated:数据更新后调用,虚拟DOM重新渲染和打补丁完成。此时可以访问更新后的DOM。需要注意的是,在这个钩子函数中如果再次修改数据,可能会导致无限循环更新。
<template>
  <div id="app">
    <input v-model="message">
    <p>{{ message }}</p>
  </div>
</template>
<script>
export default {
  data() {
    return {
      message: 'Hello'
    }
  },
  updated() {
    console.log('数据已更新,新值:', this.message);
  }
}
</script>
  1. beforeDestroy:实例销毁之前调用,在这一步,实例仍然完全可用。可以在这个钩子函数中进行一些清理操作,如清除定时器、解绑事件监听器等。
<template>
  <div id="app">
    <button @click="destroy">销毁实例</button>
  </div>
</template>
<script>
export default {
  data() {
    return {
      timer: null
    }
  },
  created() {
    this.timer = setInterval(() => {
      console.log('定时器在运行');
    }, 1000);
  },
  beforeDestroy() {
    clearInterval(this.timer);
    console.log('实例即将销毁,清除定时器');
  },
  methods: {
    destroy() {
      this.$destroy();
    }
  }
}
</script>
  1. destroyed:实例销毁后调用,此时所有的指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁。
<template>
  <div id="app">
    <button @click="destroy">销毁实例</button>
  </div>
</template>
<script>
export default {
  data() {
    return {
      timer: null
    }
  },
  created() {
    this.timer = setInterval(() => {
      console.log('定时器在运行');
    }, 1000);
  },
  beforeDestroy() {
    clearInterval(this.timer);
    console.log('实例即将销毁,清除定时器');
  },
  destroyed() {
    console.log('实例已销毁');
  },
  methods: {
    destroy() {
      this.$destroy();
    }
  }
}
</script>

Vue 3生命周期钩子函数的变化

  1. 名称变化:Vue 3中部分生命周期钩子函数的名称发生了变化,主要是为了与Composition API更好地结合。例如,beforeDestroy 改为 beforeUnmountdestroyed 改为 unmountedbeforeUpdate 改为 beforeUpdate(虽然名称未变,但在Composition API中的使用方式有所不同),updated 改为 updated(同样使用方式有变化)。这种变化是为了更准确地描述组件从挂载到卸载过程中的不同阶段,同时也与新的API设计理念保持一致。
  2. 与Composition API的结合:在Vue 3的Composition API中,生命周期钩子函数的使用方式发生了较大变化。在Vue 2中,生命周期钩子函数是定义在组件的选项对象中的,而在Vue 3的Composition API中,我们使用 setup 函数来定义组件逻辑,生命周期钩子函数通过导入特定的函数来使用。例如,要使用 created 钩子函数,在Composition API中我们这样做:
<template>
  <div>{{ message }}</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';

const message = ref('Hello');

onMounted(() => {
  console.log('组件已挂载');
});
</script>

这里的 onMounted 函数就是Vue 3中 mounted 生命周期钩子函数在Composition API中的使用方式。这种方式使得我们可以更灵活地组织代码,将相关的逻辑组合在一起,而不是像Vue 2那样将所有的生命周期钩子函数都放在组件选项对象中。

Vue 3生命周期钩子函数详解

  1. setup 函数中的生命周期钩子导入:在 setup 函数中,我们通过导入相应的函数来使用生命周期钩子。常用的导入函数有 onBeforeMountonMountedonBeforeUpdateonUpdatedonBeforeUnmountonUnmounted 等。
<template>
  <div id="app">{{ message }}</div>
</template>
<script setup>
import { ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue';

const message = ref('Hello');

onBeforeMount(() => {
  console.log('组件即将挂载');
});

onMounted(() => {
  console.log('组件已挂载');
});

onBeforeUpdate(() => {
  console.log('数据即将更新');
});

onUpdated(() => {
  console.log('数据已更新');
});

onBeforeUnmount(() => {
  console.log('组件即将卸载');
});

onUnmounted(() => {
  console.log('组件已卸载');
});
</script>
  1. onBeforeMount:在组件挂载到DOM之前调用,类似于Vue 2中的 beforeMount。在这个钩子函数中,可以对即将挂载的组件进行一些初始化操作,如设置一些全局变量、初始化第三方库的配置等。
<template>
  <div id="app">{{ message }}</div>
</template>
<script setup>
import { ref, onBeforeMount } from 'vue';

const message = ref('Hello');

onBeforeMount(() => {
  // 假设这里初始化一个全局的日志记录器
  console.log('初始化日志记录器');
});
</script>
  1. onMounted:组件挂载到DOM后调用,与Vue 2中的 mounted 类似。可以在这个钩子函数中进行DOM操作、初始化需要DOM元素的第三方插件等。
<template>
  <div id="app">
    <canvas id="myChart"></canvas>
  </div>
</template>
<script setup>
import { onMounted } from 'vue';
import Chart from 'chart.js';

onMounted(() => {
  const ctx = document.getElementById('myChart').getContext('2d');
  new Chart(ctx, {
    type: 'line',
    data: {
      labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
      datasets: [{
        label: 'My First dataset',
        data: [65, 59, 80, 81, 56, 55, 40],
        fill: false,
        borderColor: 'rgb(75, 192, 192)',
        tension: 0.1
      }]
    }
  });
});
</script>
  1. onBeforeUpdate:在数据更新导致组件重新渲染之前调用,与Vue 2中的 beforeUpdate 类似。可以在这个钩子函数中对即将更新的数据进行一些预处理操作,如数据验证、备份旧数据等。
<template>
  <div id="app">
    <input v-model="message">
    <p>{{ message }}</p>
  </div>
</template>
<script setup>
import { ref, onBeforeUpdate } from 'vue';

const message = ref('Hello');

onBeforeUpdate(() => {
  console.log('数据即将更新,当前值:', message.value);
});
</script>
  1. onUpdated:在数据更新导致组件重新渲染完成后调用,类似于Vue 2中的 updated。需要注意在这个钩子函数中避免再次修改数据导致无限循环更新。可以在这个钩子函数中进行一些依赖于更新后DOM状态的操作。
<template>
  <div id="app">
    <input v-model="message">
    <p>{{ message }}</p>
  </div>
</template>
<script setup>
import { ref, onUpdated } from 'vue';

const message = ref('Hello');

onUpdated(() => {
  console.log('数据已更新,新值:', message.value);
});
</script>
  1. onBeforeUnmount:在组件卸载之前调用,取代了Vue 2中的 beforeDestroy。可以在这个钩子函数中进行一些清理操作,如清除定时器、解绑事件监听器等。
<template>
  <div id="app">
    <button @click="unmount">卸载组件</button>
  </div>
</template>
<script setup>
import { ref, onBeforeUnmount } from 'vue';

const timer = ref(null);

onBeforeUnmount(() => {
  if (timer.value) {
    clearInterval(timer.value);
    console.log('清除定时器');
  }
});

const unmount = () => {
  // 这里假设通过某种方式卸载组件
  console.log('开始卸载组件');
};
</script>
  1. onUnmounted:在组件卸载完成后调用,取代了Vue 2中的 destroyed。此时组件相关的所有资源都已被清理。
<template>
  <div id="app">
    <button @click="unmount">卸载组件</button>
  </div>
</template>
<script setup>
import { onUnmounted } from 'vue';

onUnmounted(() => {
  console.log('组件已卸载');
});

const unmount = () => {
  // 这里假设通过某种方式卸载组件
  console.log('开始卸载组件');
};
</script>

Vue 2到Vue 3生命周期钩子的迁移指南

  1. 钩子函数名称替换:首先,需要将Vue 2中的 beforeDestroy 替换为 beforeUnmountdestroyed 替换为 unmounted。这是最基本的名称变化迁移。
  2. 选项式API到Composition API的迁移:如果使用的是选项式API(即Vue 2中常用的方式),并且想要迁移到Vue 3的Composition API,需要将生命周期钩子函数的定义方式进行改变。例如,将Vue 2中的 created 钩子函数:
export default {
  data() {
    return {
      message: 'Hello'
    }
  },
  created() {
    console.log('组件创建完成');
  }
}

迁移到Vue 3的Composition API中:

<template>
  <div>{{ message }}</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';

const message = ref('Hello');

onMounted(() => {
  console.log('组件创建完成');
});
</script>
  1. 数据访问和作用域:在Vue 2的选项式API中,this 指向Vue实例,可以直接访问 datamethods 等属性。而在Vue 3的Composition API中,setup 函数中的 this 并不指向Vue实例。在 setup 函数中定义的数据和函数需要通过 refreactive 等函数进行处理,并且在模板中使用时也有不同的方式。例如,在Vue 2中:
<template>
  <div>{{ message }}</div>
</template>
<script>
export default {
  data() {
    return {
      message: 'Hello'
    }
  },
  created() {
    this.message = 'World';
  }
}
</script>

在Vue 3的Composition API中:

<template>
  <div>{{ message }}</div>
</template>
<script setup>
import { ref } from 'vue';

const message = ref('Hello');

// 这里不能直接用this.message,需要使用message.value
setTimeout(() => {
  message.value = 'World';
}, 1000);
</script>
  1. 复杂组件逻辑迁移:对于复杂的组件逻辑,在迁移过程中需要注意将相关的生命周期钩子函数逻辑按照Vue 3的方式进行整理。例如,如果在Vue 2中有一个组件,在 mounted 钩子函数中进行了大量的DOM操作和第三方插件初始化,并且还依赖于其他 data 属性,在迁移到Vue 3时,需要将这些逻辑合理地分布在 setup 函数中的 onMounted 钩子函数以及相关的数据定义和处理中。
<!-- Vue 2组件 -->
<template>
  <div id="app">
    <canvas id="myChart"></canvas>
  </div>
</template>
<script>
export default {
  data() {
    return {
      chartData: {
        labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
        datasets: [{
          label: 'My First dataset',
          data: [65, 59, 80, 81, 56, 55, 40],
          fill: false,
          borderColor: 'rgb(75, 192, 192)',
          tension: 0.1
        }]
      }
    };
  },
  mounted() {
    const ctx = this.$el.querySelector('#myChart').getContext('2d');
    new Chart(ctx, {
      type: 'line',
      data: this.chartData
    });
  }
}
</script>
<!-- Vue 3组件 -->
<template>
  <div id="app">
    <canvas id="myChart"></canvas>
  </div>
</template>
<script setup>
import { reactive, onMounted } from 'vue';
import Chart from 'chart.js';

const chartData = reactive({
  labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
  datasets: [{
    label: 'My First dataset',
    data: [65, 59, 80, 81, 56, 55, 40],
    fill: false,
    borderColor: 'rgb(75, 192, 192)',
    tension: 0.1
  }]
});

onMounted(() => {
  const ctx = document.getElementById('myChart').getContext('2d');
  new Chart(ctx, {
    type: 'line',
    data: chartData
  });
});
</script>
  1. 事件监听和解绑:在Vue 2中,我们可能在 createdmounted 钩子函数中添加事件监听器,并在 beforeDestroy 钩子函数中解绑。在Vue 3中,使用 onMounted 添加事件监听器,并在 onBeforeUnmount 中解绑。
<!-- Vue 2组件 -->
<template>
  <div id="app"></div>
</template>
<script>
export default {
  created() {
    window.addEventListener('resize', this.handleResize);
  },
  beforeDestroy() {
    window.removeEventListener('resize', this.handleResize);
  },
  methods: {
    handleResize() {
      console.log('窗口大小改变');
    }
  }
}
</script>
<!-- Vue 3组件 -->
<template>
  <div id="app"></div>
</template>
<script setup>
import { onMounted, onBeforeUnmount } from 'vue';

const handleResize = () => {
  console.log('窗口大小改变');
};

onMounted(() => {
  window.addEventListener('resize', handleResize);
});

onBeforeUnmount(() => {
  window.removeEventListener('resize', handleResize);
});
</script>

通过以上步骤和示例,我们可以较为顺利地将Vue 2中的生命周期钩子函数相关逻辑迁移到Vue 3中,无论是从名称替换、API使用方式改变,还是复杂逻辑的重新组织,都能找到对应的方法,使得我们能够更好地利用Vue 3的新特性进行前端开发。