網(wǎng)站后臺(tái)是怎么做的網(wǎng)頁(yè)怎么做出來(lái)的
前言
對(duì)于 vue3 的生命周期,我們經(jīng)常性會(huì)去疑問(wèn),生命周期有哪些呢,它是怎么去實(shí)現(xiàn)的, 又是什么時(shí)候調(diào)用的。
vue3 生命周期有哪些
下面這個(gè)表格列出了所有選項(xiàng)式api生命周期鉤子和組合式api生命周期鉤子,以及他們的對(duì)應(yīng)關(guān)系和執(zhí)行的時(shí)機(jī)。
composition api | options api | 執(zhí)行時(shí)機(jī) |
---|---|---|
— | beforeCreate | 初始化組件內(nèi)的屬性(如:data,props,watch,computed等)之前 |
— | created | 初始化組件內(nèi)的屬性(如:data,props,watch,computed等)之后 |
onBeforeMount | beforeMount | 組件開(kāi)始掛載之前 |
onMounted | mounted | 組件掛載之后 |
onBeforeUpdate | beforeUpdate | 組件數(shù)據(jù)更新之后,頁(yè)面更新之前 |
onUpdated | updated | 組件數(shù)據(jù)更新之后,頁(yè)面更新之后 |
onBeforeUnmount | beforeUnmount | 組件即將卸載,但還未卸載 |
onUnmounted | unmounted | 組件卸載之后 |
onErrorCaptured | errorCaptured | 捕獲了后代組件傳遞的錯(cuò)誤時(shí) |
onRenderTracked | renderTracked | 響應(yīng)式依賴(lài)被組件的渲染作用追蹤后,僅開(kāi)發(fā)模式下使用 |
onRenderTriggered | renderTriggered | 響應(yīng)式依賴(lài)被組件觸發(fā)了重新渲染之后,僅開(kāi)發(fā)模式下使用 |
onActivated | activated | 組件被keep-alive包裹,頁(yè)面從不活動(dòng)狀態(tài)變?yōu)榛顒?dòng)狀態(tài)執(zhí)時(shí) |
onDeactivated | deactivated | 組件被keep-alive包裹,頁(yè)面從活動(dòng)狀態(tài)變?yōu)椴换顒?dòng)狀態(tài)執(zhí)時(shí) |
onServerPrefetch | serverPrefetch | 組件實(shí)例在服務(wù)器上被渲染之前,為異步函數(shù),僅ssr模式使用 |
生命周期是如何掛載到實(shí)例化的
首先,我們可以根據(jù) onBeforeMount、onMounted 的源碼定義,可以看出定義鉤子函數(shù)時(shí)是直接調(diào)用 injectHook 掛載在實(shí)例化上。
import { currentInstance, setCurrentInstance } from './component'const enum LifecycleHooks {BEFORE_MOUNT = 'bm',MOUNTED = 'm',BEFORE_UPDATE = 'bu',UPDATED = 'u',BEFORE_UNMOUNT = 'bum',UNMOUNTED = 'um',
}export const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT)
export const onMounted = createHook(LifecycleHooks.MOUNTED)
export const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE)
export const onUpdated = createHook(LifecycleHooks.UPDATED)
export const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT)
export const onUnmounted = createHook(LifecycleHooks.UNMOUNTED)// 核心就是這個(gè)生命周期函數(shù)要和當(dāng)前組件產(chǎn)生關(guān)聯(lián)
function createHook (lifecycle) {return function (hook, target = currentInstance) { // hook 自定義方法// 獲取到當(dāng)前組件的實(shí)例和生命周期產(chǎn)生關(guān)聯(lián)injectHook(lifecycle, hook, target)}
}/*** * @param type bm、m、bu、u等代表生命周期的簡(jiǎn)寫(xiě)變量* @param hook 自定義的方法* @param target 當(dāng)前 vue 實(shí)例*/
function injectHook (type, hook, target) {if (target) {const hooks = target[type] || (target[type] = [])const wrappedHook = () => {setCurrentInstance(target)hook()setCurrentInstance(null)}hooks.push(wrappedHook) // 掛載的時(shí)候,直接放到對(duì)應(yīng)的數(shù)組里面}
}
這段代碼的作用是定義了一系列組件生命周期鉤子函數(shù),并將傳入的自定義鉤子函數(shù)與當(dāng)前組件實(shí)例關(guān)聯(lián)起來(lái)。
生命周期如何調(diào)用、觸發(fā)
首先,通過(guò) injectHook 函數(shù)我們知道,每個(gè)生命周期鉤子函數(shù)都是存放到實(shí)例化的變量里面,所以,要先從實(shí)例化里面取出。例如:
const { bm, m } = instance // 獲取
if (bm) {invokeArrayFns(bm)
}
其次,用戶(hù)自定義的 hook 函數(shù)都是存放到對(duì)應(yīng)的生命周期函數(shù)的數(shù)組中,所以調(diào)用的時(shí)候直接遍歷數(shù)組執(zhí)行即可。
const invokeArrayFns = (fns) => {for (let i = 0; i < fns.length; i++) {fns[i]()}
}
onBeforeMount、onMounted、onBeforeUpdate、onUpdated
從之前的篇文章中我們知道 createApp
函數(shù)最為重要的一個(gè)函數(shù)是 mount
,在 mount
函數(shù)中會(huì)調(diào)用mountComponent
經(jīng)行組件的掛載。我們可以從 mountComponent
函數(shù)為口入逐步分析生命周期執(zhí)行的過(guò)程。
mountComponent
mountComponent 函數(shù)用于掛載組件類(lèi)型的虛擬 DOM 節(jié)點(diǎn)。
函數(shù)的作用是創(chuàng)建組件實(shí)例、解析組件實(shí)例對(duì)象并設(shè)置渲染效果。
const mountComponent = (initialVNode, container) => {const instance = initialVNode.component = createComponentInstance(initialVNode)setupComponent(instance) // 解析組件的實(shí)例對(duì)象setupRenderEffect(instance, container)
}
createComponentInstance
初始化一個(gè)組件的實(shí)例對(duì)象,添加相關(guān)屬性(vnode、type、props、attrs、ctx、proxy)。
const createComponentInstance = (vnode) => {const instance = {vnode,type: vnode.type, // 組件的類(lèi)型props: {}, // 組件的屬性attrs: {},setupState: {}, // setup返回值ctx: {}, // proxy.props.name -> proxy.nameproxy: {},render: false,isMounted: false, // 是否掛載children: []}instance.ctx = { _: instance } // 實(shí)例上下文return instance
}
setupRenderEffect
setupRenderEffect 創(chuàng)建一個(gè) effect,用于在組件狀態(tài)變化時(shí)觸發(fā)重新渲染。
該 effect 依賴(lài)于組件實(shí)例的響應(yīng)式數(shù)據(jù),當(dāng)響應(yīng)式數(shù)據(jù)發(fā)生變化時(shí),會(huì)觸發(fā)該 effect,進(jìn)而調(diào)用組件實(shí)例的 render 方法重新渲染組件,并將渲染結(jié)果插入到 container 容器中。
import { effect } from '@vue/reactivity'const setupRenderEffect = (instance, container) => {// 創(chuàng)建響應(yīng)式的副作用渲染函數(shù)effect(function componentsEffect () {// 判斷第一次加載// 執(zhí)行render,獲取方法中的依賴(lài)收集effect,屬性改變?cè)俅螆?zhí)行if (!instance.isMounted) {// beforeMount hookconst { bm, m } = instanceif (bm) {invokeArrayFns(bm)}const proxyToUse = instance.proxy // 組件的實(shí)例const subTree = instance.subTree = instance.render.call(proxyToUse,proxyToUse) // 渲染組件生成子樹(shù)vnode// 將子樹(shù)vnode掛載到container上patch(null, subTree, container)instance.isMounted = true// 渲染完成 mounted hookif (m) {invokeArrayFns(m)}} else {const { u, bu } = instance// beforeUpdate hookif (bu) {invokeArrayFns(bu)}const proxyToUse = instance.proxy // 組件的實(shí)例const prevTree = instance.subTree // 保留渲染生成的子樹(shù)根DOM節(jié)點(diǎn)const nextTree = instance.render.call(proxyToUse, proxyToUse) // 獲取子組件樹(shù)instance.subTree = nextTree // 更新當(dāng)前的子組件樹(shù)patch(prevTree, nextTree, container) // 渲染新的子組件樹(shù)到容器中// update hookif (u) {invokeArrayFns(u)}}})}
在執(zhí)行組件掛載之前,會(huì)檢測(cè)組件實(shí)例上是否存在注冊(cè)的 beforeMount
鉤子函數(shù)(bm)。
如果存在,通過(guò)遍歷 instance.bm
數(shù)組并使用 invokeArrayFns
方法依次執(zhí)行這些鉤子函數(shù)。
這樣設(shè)計(jì)的原因是用戶(hù)可以通過(guò)多次調(diào)用 onBeforeMount
函數(shù)來(lái)注冊(cè)多個(gè) beforeMount
鉤子函數(shù),保證它們按注冊(cè)順序依次執(zhí)行。
onBeforeUnmount、onUnmounted
在之前分析 patch 函數(shù)的執(zhí)行流程中我們知道,當(dāng)存在舊節(jié)點(diǎn)并且新舊節(jié)點(diǎn)的類(lèi)型不同時(shí),會(huì)先卸載舊節(jié)點(diǎn),然后進(jìn)行新節(jié)點(diǎn)的渲染和掛載。用戶(hù)也可以手動(dòng)調(diào)用 unmount 函數(shù)經(jīng)行卸載。
unmount
unmount 函數(shù)的作用是執(zhí)行組件的卸載操作。
// patching & not same type, unmount old tree
if (n1 && !isSameVNodeType(n1, n2)) {anchor = getNextHostNode(n1)unmount(n1, parentComponent, parentSuspense, true)n1 = null
}
unmount 函數(shù)的作用包括以下幾個(gè)方面:
-
從父組件中移除:unmount 函數(shù)會(huì)將組件從其父組件中移除。這意味著組件將不再在父組件的模板中渲染,并且在父組件的組件實(shí)例中,該組件的相關(guān)引用將被清除。
-
卸載組件實(shí)例:unmount 函數(shù)會(huì)執(zhí)行組件實(shí)例的卸載過(guò)程。在卸載過(guò)程中,vue 3 會(huì)依次觸發(fā)生命周期鉤子函數(shù),包括 onBeforeUnmount 和 unmounted。你可以在這些鉤子函數(shù)中執(zhí)行一些清理操作或釋放資源的操作。
-
移除 DOM 元素:unmount 函數(shù)會(huì)將組件的根 DOM 元素從 DOM 樹(shù)中移除,以及解綁組件與其根 DOM 元素之間的關(guān)聯(lián)。這樣,組件的模板內(nèi)容將不再顯示在頁(yè)面上,并且與 DOM 相關(guān)的事件監(jiān)聽(tīng)器和其他綁定也將被清除。
在 unmount 函數(shù)中如果 ShapeFlags 類(lèi)型是 COMPONENT 的話(huà)會(huì)執(zhí)行 unmountComponent 函數(shù)經(jīng)行組件的卸載。
unmountComponent
unmountComponent 函數(shù)的作用是卸載組件實(shí)例。為了便于理解,以下代碼為簡(jiǎn)化之后的代碼:
const unmountComponent = (instance,parentSuspense,doRemove,
) => {const { bum, um } = instance// beforeUnmount hookif (bum) {invokeArrayFns(bum)}// stop effects in component scopescope.stop()// unmounted hookif (um) {queuePostRenderEffect(um, parentSuspense) // 這里會(huì)循環(huán) um 里面的函數(shù)}
}
總結(jié)
希望通過(guò)本篇文章可以幫助讀者更好的了解生命周期的執(zhí)行過(guò)程。