当涉及到 Vue.js 的生命周期时,Vue 3 提供了一些新的概念和方式来管理组件的生命周期。在本文中,我们将深入探讨 Vue 3 的生命周期,并介绍每个生命周期钩子的作用和使用场景。
Vue 3 的生命周期概述
在 Vue 3 中,组件的生命周期主要分为三个阶段:
- 创建阶段(Initialization)
- 更新阶段(Mounting and Updating)
- 销毁阶段(Unmounting)
每个阶段都有相应的生命周期钩子函数,可以让开发者在不同的阶段执行特定的逻辑。
下面的例子中左边为选项式Api右边是组合式Api
创建阶段(Initialization)
在创建阶段,组件的实例和其相关的数据正在被初始化。这个阶段包括以下几个生命周期钩子:
beforeCreate
- 触发时机: 在实例初始化之后,数据观测 (
data
和props
的初始化) 之前被调用。 - 使用场景: 适合在组件实例被创建之前执行一些初始化工作,但此时组件实例还未完全被创建,因此无法访问到
data
、props
、methods
和computed
等成员。通常在此阶段处理一些全局配置或初始化非响应式的数据。
<script>
export default {
beforeCreate() {
console.log('beforeCreate hook triggered');
console.log('Data is not reactive yet:', this.dataValue); // undefined
this.nonReactiveData = 'Initial non-reactive data';
},
data() {
return {
dataValue: 'Hello, Vue!'
};
}
};
</script>
在 beforeCreate
钩子中,我们可以看到 dataValue
是不可访问的,因为此时 data
还未被初始化。但我们可以初始化非响应式的数据 this.nonReactiveData
。
created
- 触发时机: 在实例创建完成后被调用,此时组件实例已完成数据观测 (
data
和props
的初始化),但是 DOM 元素还未生成,因此无法访问到$el
。 - 使用场景: 适合执行一些初始化操作,如异步请求数据、对数据的进一步处理,或者在组件创建完成后进行一些操作,如设置定时器、监听事件等。
<script>
export default {
data() {
return {
message: ''
};
},
created() {
console.log('created hook triggered');
console.log('Data is reactive now:', this.message); // 'Hello, Vue!'
this.fetchData();
},
methods: {
async fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
this.message = data.message;
} catch (error) {
console.error('Error fetching data:', error);
}
}
}
};
</script>
在 created
钩子中,我们可以访问和操作 data
中的数据,因为此时 Vue 已完成数据观测,数据是响应式的。在示例中,我们通过 fetchData
方法异步获取数据,并将其赋值给 message
。
组合式Api中不存在created和beforeCreate 基本上就是用setup代替
更新阶段(Mounting and Updating)
更新阶段是组件实例被创建之后,数据发生变化导致重新渲染时的阶段。这里涵盖了更多的生命周期钩子(括号里面为组合式Api对应的方法):
- beforeMount(onBeforeMount):在挂载开始之前被调用:相关的 render 函数首次被调用。
- mounted(onMounted):el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子函数。
- beforeUpdate(onBeforeUpdate):数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。
- updated(onUpdated):由于数据更改导致的虚拟 DOM 重新渲染和打补丁后调用。
<template>
<div>
<p>{{ message }}</p>
<button @click="updateMessage">Update Message</button>
</div>
</template>
<script>
import { ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated } from 'vue';
export default {
setup() {
const message = ref('Hello, Vue!');
// 在组合式 API 中使用生命周期钩子
onBeforeMount(() => {
console.log('Component is about to be mounted');
});
onMounted(() => {
console.log('Component is mounted');
});
onBeforeUpdate(() => {
console.log('Component is about to update');
});
onUpdated(() => {
console.log('Component is updated');
});
const updateMessage = () => {
message.value = 'Updated message!';
};
return {
message,
updateMessage
};
},
};
</script>
上面代码中,页面加载之后onBeforeMount和onMount顺序执行,点击按钮onBeforeUpdate获取到的还是变更之前的数据,onUpdate获取到的是变更之后的数据。
销毁阶段(Unmounting)
在组件实例被销毁时,销毁阶段的生命周期钩子函数将会被调用:
- beforeUnmount(onBeforeUnmount):在卸载之前调用。在这一步,实例仍然完全可用。
- unmounted(onUnmounted):在卸载完成后调用。该钩子函数用来清理一些组件内的事件监听器或定时器等资源。
当我们离开一个vue界面到达另一个vue界面时,有些未关闭的东西例如定时器是不会关闭的,这将消耗我们的系统资源,我们可以使用这两个方法来关闭定时器
const startTimer = () => {
timerId.value = setInterval(() => {
console.log('定时器执行中')
}, 1000)
}
//如果不加,定时器一旦开始,返回其他页面时定时器根本不会被销毁,而是一直在执行
onUnmounted(() => {
console.log('组件将被卸载')
if (timerId.value) {
clearInterval(timerId.value)
}
})
onBeforeUnmount(() => {
console.log('组件即将被卸载')
// 在组件卸载前执行的逻辑,例如清除定时器、取消订阅等
if (timerId.value) {
clearInterval(timerId.value)
}
})
在 Vue 3 的组合式 API 中,除了常见的生命周期钩子(如 onBeforeMount
、onMounted
、onBeforeUpdate
、onUpdated
)外,还引入了一些新的生命周期钩子和钩子函数,用于更细粒度地控制和监控组件的行为。下面是这些生命周期钩子的详细介绍及使用方式:
errorCaptured(onErrorCaptured)
- 作用:用于捕获组件及其子组件的任何错误。它常常用于捕获子组件报的错误,自己的错误好像捕获不了。
- 参数:
error
:捕获到的错误对象。instance
:出现错误的组件实例。info
:关于错误的额外信息。
- 返回值:钩子可以通过返回
false
来阻止错误继续向上传递。
使用示例:
<LifeCycleError/>
// 使用 onErrorCaptured 捕获错误 自己的错误捕捉不到,只能捕捉子组件的内容
onErrorCaptured((error, component, info) => {
// 记录错误信息
errorRef.value = `Error: ${error.message}`;
// 可以选择处理错误,或者在控制台打印错误信息
console.error('Error captured in component:', component);
console.error('Error info:', info);
// 返回 false 表示阻止错误继续传播
return true;
});
子组件中:
function fetchData(){
let json = JSON.parse("{sa:as}")
}
这个方法会出现JSON转化异常,就可以被父页面中捕获。
activated(onActivated) 和 deactivated(onDeactivated)
onActivated 和 onDeactivated 用于监听组件的激活和失活状态变化。
使用示例:
<div v-if="flag">
<keep-alive>
<LifeCycleError/>
</keep-alive>
</div>
<div v-else>
<keep-alive>
<WelcomeItem/>
</keep-alive>
子组件中:
onActivated(() => {
console.log('Component is activated');
});
onDeactivated(() => {
console.log('Component is deactivated');
});
当flag属性=true后。子组件中的onActivated会被激活,当为false之后,onDeactivated属性会被激活。
renderTriggered(onRenderTriggered)(Only dev)
- 功能: 当响应式数据引起重新渲染时触发的钩子函数。
- 使用场景: 主要用于调试和性能分析,可以在数据变化时触发自定义逻辑或记录日志,以便更好地理解何时和为什么会触发重新渲染。
示例:
import { onRenderTriggered } from '@vue/reactivity';
onRenderTriggered((event) => {
console.log('Render triggered!', event);
// 可以在这里执行自定义的逻辑
});
在上面的例子中,我们使用 onRenderTriggered
函数注册了一个回调函数,当响应式数据引起重新渲染时,会触发这个回调函数,并且会传入一个事件对象 event
,可以用于进一步分析和调试。
renderTracked(onRenderTracked)(Only dev)
- 功能: 当响应式数据被追踪到(即被访问)时触发的钩子函数。
- 使用场景: 同样用于调试和性能分析,可以在数据被访问时触发自定义逻辑或记录日志,帮助理解数据的访问情况和优化性能。
示例:
import { onRenderTracked } from '@vue/reactivity';
onRenderTracked((event) => {
console.log('Render tracked!', event);
// 可以在这里执行自定义的逻辑
});
在这个示例中,我们使用 onRenderTracked
函数注册了一个回调函数,当响应式数据被追踪到时,会触发这个回调函数,并且同样会传入一个事件对象 event
,用于进一步分析和调试。
serverPrefetch (onServerPrefetch)
在组件的 setup
函数中,通过 onServerPrefetch
钩子定义在服务端预取数据时执行的逻辑。
import { onServerPrefetch, ref } from 'vue';
export default {
setup() {
const data = ref(null);
// 在服务端预取数据时执行的逻辑
onServerPrefetch(async () => {
// 模拟异步获取数据
const result = await fetchData();
data.value = result;
});
// 返回组件内部需要的数据或状态
return {
data
};
}
};
-
服务端预取数据的执行环境
onServerPrefetch
只在服务端执行,不会在客户端执行。这意味着你可以在此钩子内执行一些服务端专有的操作,比如通过 API 请求数据或者直接操作数据库。- Vue 会在服务端渲染期间自动调用
onServerPrefetch
钩子,并等待其完成,然后将结果一并传递到客户端。
-
客户端的数据同步
当客户端接管页面并开始交互时,Vue 将使用服务端预取的数据初始化组件状态,避免不必要的数据请求。
-
注意事项
- 在
onServerPrefetch
中可以使用async
函数来处理异步逻辑,确保在数据获取完成前不会返回组件实例。 - 可以在
onServerPrefetch
中执行任何服务端可用的操作,但需注意不要包含依赖于客户端环境的逻辑。 onServerPrefetch
使用条件比较苛刻,后期在SSR章节中详细解释一下这玩意。
- 在
下面是我随意写了个小例子:
LifeCycleError.vue
<!--条件和列表渲染-->
<template>
<button @click="fetchData">handleFetchData</button>
{{ zhangsan }}
</template>
<script lang="ts" setup>
import { computed, nextTick } from 'vue'
import { ref } from 'vue'
import { reactive } from 'vue'
import { ElMessage } from 'element-plus'
import { onMounted,
onUpdated,
onUnmounted,
onBeforeMount,
onBeforeUpdate,
onBeforeUnmount,
onRenderTracked,
onRenderTriggered,onServerPrefetch,
onErrorCaptured , onActivated,
onDeactivated} from 'vue'
function fetchData(){
let json = JSON.parse("{sa:as}")
}
const zhangsan=ref("四")
onRenderTracked((event) => {
zhangsan.value="wu"
console.log(`Render tracked for "${event.target}"`);
});
onRenderTriggered((event) => {
zhangsan.value="liu"
console.log(`Render triggered for "${event.target}"`);
});
onActivated(() => {
console.log('Component is activated');
});
onDeactivated(() => {
console.log('Component is deactivated');
});
</script>
<style scoped>
</style>
LifeCycle.vue
<!--条件和列表渲染-->
<template>
<div id="vue2">
</div>
<div id="vue3">
<button id="count" @click="count++">{{ count }}</button>
<button @click="fetchData2">handleFetchData2</button>
<div v-for="user in userAgeltTen">
{{ user.userName }}
</div>
<div v-if="flag">
<keep-alive>
<LifeCycleError/>
</keep-alive>
</div>
<div v-else>
<keep-alive>
<WelcomeItem/>
</keep-alive>
</div>
</div>
{{ data2 }}
</template>
<script lang="ts">
import LifeCycleError from '../../components/LifeCycleError.vue'
import WelcomeItem from '../../components/WelcomeItem.vue'
export default {
data() {
return {
}
},
beforeCreate(){
},
methods: {
}
}
</script>
<script lang="ts" setup>
import { computed, nextTick } from 'vue'
import { ref } from 'vue'
import { reactive } from 'vue'
import { ElMessage } from 'element-plus'
import { inject,createSSRApp } from 'vue';
import { onMounted,
onUpdated,
onUnmounted,
onBeforeMount,
onBeforeUpdate,
onBeforeUnmount,
onErrorCaptured,
onServerPrefetch
} from 'vue'
const flag = ref(true)
const el = ref("zhangsan")
onMounted(() => {
console.log(`vue3-onMounted-`+el.value)
})
const count = ref(0)
onUpdated(() => {
// 文本内容应该与当前的 `count.value` 一致
//数据被更新时就会调用,但是这里写的时候不要用el-button,可能抓不到
console.log("onUpdated-"+document.getElementById('count').textContent)
startTimer()
})
onBeforeUpdate(() => {
// 文本内容应该与当前的 `count.value` 一致,点击一下此时还是0
console.log("onBeforeUpdate-"+document.getElementById('count').textContent)
startTimer()
})
const timerId = ref()
const startTimer = () => {
timerId.value = setInterval(() => {
console.log('定时器执行中')
}, 1000)
}
//如果不加,定时器一旦开始,返回其他页面时定时器根本不会被销毁,而是一直在执行
onUnmounted(() => {
console.log('组件将被卸载')
if (timerId.value) {
clearInterval(timerId.value)
}
})
onBeforeUnmount(() => {
console.log('组件即将被卸载')
// 在组件卸载前执行的逻辑,例如清除定时器、取消订阅等
if (timerId.value) {
clearInterval(timerId.value)
}
})
const data = ref();
//如果不加,定时器一旦开始,返回其他页面时定时器根本不会被销毁,而是一直在执行,一般用来初始化数据,create差不多,但是时机不同
onBeforeMount(() => {
data.value = [
"zhangsan","lisi"
]
console.log(data.value);
})
function fetchData2(){
userVue[1].userName = "wangwu"
flag.value = !flag.value
let json = JSON.parse("{sa:as}")
}
const errorRef = ref();
// 使用 onErrorCaptured 捕获错误 自己的错误捕捉不到,只能捕捉子组件的内容
onErrorCaptured((error, component, info) => {
// 记录错误信息
errorRef.value = `Error: ${error.message}`;
// 可以选择处理错误,或者在控制台打印错误信息
console.error('Error captured in component:', component);
console.error('Error info:', info);
// 返回 false 表示阻止错误继续传播
return true;
});
//定义一个数组
let users = [
{
userName: 'zhangsan',
age: 9,
sex: 'man'
},
{
userName: 'lisi',
age: 12,
sex: 'woman'
}
]
let userVue = reactive(users)
const userAgeltTen = computed(() => {
console.log("计算属性执行了")
return userVue.filter(user=>user.age>10)
})
//仅在SSR环境执行
onServerPrefetch(async () => {
console.log('Fetching data on server...');
});
</script>
<style scoped>
</style>
关注公众号:资小库,问题快速答疑解惑