本文使用的vue版本为3.0.5
- createApp() & mount()
- setup()
- render() h()
- Virtual Dom
- 生命周期hooks
- Proxy代理
- reactive() ref()
- computed()
- watch()
- provide() inject()
- directives()
- components()
差异
相较于2.x版本,生命周期部分的改动并不大。
从官方文档我们可以知道,使用setup()
的方式注册生命周期事件,需要从外部导入对应的注册函数,名称则是在老版本的名称前加上on
前缀,同时需要注意驼峰格式。
import {onBeforeMount,onMounted, ...} from 'vue'
Options API | Hook inside setup |
---|---|
beforeCreate | Not needed* |
created | Not needed* |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeUnmount | onBeforeUnmount |
unmounted | onUnmounted |
errorCaptured | onErrorCaptured |
renderTracked | onRenderTracked |
renderTriggered | onRenderTriggered |
Because
setup
is run around thebeforeCreate
andcreated
lifecycle hooks, you do not need to explicitly define them. In other words, any code that would be written inside those hooks should be written directly in thesetup
function.
另外,由于setup()
执行的时机与原先beforeCreate、created
钩子执行的时机几乎相同,所以无需再显式的定义这两个钩子,直接在setup
中执行即可。
源码探究
1.生命周期注册函数的实现
// packages/runtime-core/src/apiLifecycle.ts
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)
export type DebuggerHook = (e: DebuggerEvent) => void
export const onRenderTriggered = createHook<DebuggerHook>(
LifecycleHooks.RENDER_TRIGGERED
)
export const onRenderTracked = createHook<DebuggerHook>(
LifecycleHooks.RENDER_TRACKED
)
export type ErrorCapturedHook = (
err: unknown,
instance: ComponentPublicInstance | null,
info: string
) => boolean | void
export const onErrorCaptured = (
hook: ErrorCapturedHook,
target: ComponentInternalInstance | null = currentInstance
) => {
injectHook(LifecycleHooks.ERROR_CAPTURED, hook, target)
}
2.钩子注入
export const createHook = <T extends Function = () => any>(
lifecycle: LifecycleHooks
) => (hook: T, target: ComponentInternalInstance | null = currentInstance) =>
// post-create lifecycle registrations are noops during SSR
!isInSSRComponentSetup && injectHook(lifecycle, hook, target)
3.处理函数体
export function injectHook(
type: LifecycleHooks,
hook: Function & { __weh?: Function },
target: ComponentInternalInstance | null = currentInstance,
prepend: boolean = false
): Function | undefined {
if (target) {
// 维护一个函数栈,存放所有需要执行的函数。除了开发者自定义的hook外,框架自身也有许多需要在各个hook内执行的代码。
const hooks = target[type] || (target[type] = [])
// 封装hook
const wrappedHook =
hook.__weh ||
(hook.__weh = (...args: unknown[]) => {
if (target.isUnmounted) {
return
}
// 暂停收集依赖,防止被重复收集
pauseTracking()
setCurrentInstance(target)
const res = callWithAsyncErrorHandling(hook, target, type, args)
setCurrentInstance(null)
// 恢复收集
resetTracking()
return res
})
// 是否头部入栈
if (prepend) {
hooks.unshift(wrappedHook)
} else {
hooks.push(wrappedHook)
}
return wrappedHook
} else if (__DEV__) {
const apiName = toHandlerKey(ErrorTypeStrings[type].replace(/ hook$/, ''))
warn(
`${apiName} is called when there is no active component instance to be ` +
`associated with. ` +
`Lifecycle injection APIs can only be used during execution of setup().` +
(__FEATURE_SUSPENSE__
? ` If you are using async setup(), make sure to register lifecycle ` +
`hooks before the first await statement.`
: ``)
)
}
}
4.注册
// packages/runtime-core/src/componentOptions.ts
export function applyOptions(
instance: ComponentInternalInstance,
options: ComponentOptions,
deferredData: DataFn[] = [],
deferredWatch: ComponentWatchOptions[] = [],
deferredProvide: (Data | Function)[] = [],
asMixin: boolean = false
) {
// 从配置中取出钩子函数
const {
// composition
mixins,
extends: extendsOptions,
// state
data: dataOptions,
computed: computedOptions,
methods,
watch: watchOptions,
provide: provideOptions,
inject: injectOptions,
// assets
components,
directives,
// lifecycle
beforeMount,
mounted,
beforeUpdate,
updated,
activated,
deactivated,
beforeDestroy,
beforeUnmount,
destroyed,
unmounted,
render,
renderTracked,
renderTriggered,
errorCaptured,
// public API
expose
} = options
// ...省略无关代码
if (!asMixin) {
callSyncHook(
'created',
LifecycleHooks.CREATED,
options,
instance,
globalMixins
)
}
if (beforeMount) {
onBeforeMount(beforeMount.bind(publicThis))
}
if (mounted) {
onMounted(mounted.bind(publicThis))
}
if (beforeUpdate) {
onBeforeUpdate(beforeUpdate.bind(publicThis))
}
if (updated) {
onUpdated(updated.bind(publicThis))
}
if (activated) {
onActivated(activated.bind(publicThis))
}
if (deactivated) {
onDeactivated(deactivated.bind(publicThis))
}
if (errorCaptured) {
onErrorCaptured(errorCaptured.bind(publicThis))
}
if (renderTracked) {
onRenderTracked(renderTracked.bind(publicThis))
}
if (renderTriggered) {
onRenderTriggered(renderTriggered.bind(publicThis))
}
if (__DEV__ && beforeDestroy) {
warn(`\`beforeDestroy\` has been renamed to \`beforeUnmount\`.`)
}
if (beforeUnmount) {
onBeforeUnmount(beforeUnmount.bind(publicThis))
}
if (__DEV__ && destroyed) {
warn(`\`destroyed\` has been renamed to \`unmounted\`.`)
}
if (unmounted) {
onUnmounted(unmounted.bind(publicThis))
}
}
5.执行
最后,在render过程中的对应时机,清空函数栈
// packages/runtime-core/src/renderer.ts
const setupRenderEffect: SetupRenderEffectFn = (
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
) => {
// create reactive effect for rendering
instance.update = effect(function componentEffect() {
if (!instance.isMounted) {
// 挂载流程
let vnodeHook: VNodeHook | null | undefined
const { el, props } = initialVNode
const { bm, m, parent } = instance
// beforeMount hook
if (bm) {
invokeArrayFns(bm)
}
// ...省略无关代码
// mounted hook
if (m) {
queuePostRenderEffect(m, parentSuspense)
}
// activated hook for keep-alive roots.
// #1742 activated hook must be accessed after first render
// since the hook may be injected by a child keep-alive
const { a } = instance
if (
a &&
initialVNode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
) {
queuePostRenderEffect(a, parentSuspense)
}
instance.isMounted = true
// #2458: deference mount-only object parameters to prevent memleaks
initialVNode = container = anchor = null as any
} else {
// 更新流程
let { next, bu, u, parent, vnode } = instance
let originNext = next
let vnodeHook: VNodeHook | null | undefined
// ... 省略
// beforeUpdate hook
if (bu) {
invokeArrayFns(bu)
}
// updated hook
if (u) {
queuePostRenderEffect(u, parentSuspense)
}
}
}, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
}
const unmountComponent = (
instance: ComponentInternalInstance,
parentSuspense: SuspenseBoundary | null,
doRemove?: boolean
) => {
// ... 省略
const { bum, effects, update, subTree, um } = instance
// beforeUnmount hook
if (bum) {
invokeArrayFns(bum)
}
// unmounted hook
if (um) {
queuePostRenderEffect(um, parentSuspense)
}
}
简易实现
基于之前的成果,简单实现onBeforeMount
和onMounted
两个hooks