The VUE version used in this article is 3.0.5
- createApp() & mount()
- setup()
- render() h()
- Virtual Dom
- Lifecycle hooks
- The Proxy agent
- reactive() ref()
- computed()
- watch()
- provide() inject()
- directives()
- components()
differences
Compared to the 2.x version, the changes to the life cycle are minor.
To register life cycle events using setup(), you need to import the corresponding registration function from the outside. The name of the registration function is prefixed with the on prefix of the old version name, and you need to pay attention to the hump format.
import{onBeforeMount,onMounted, ... }from 'vue'
Copy the code
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.
In addition, setup() executes at almost the same time as the original beforeCreate and Created hooks, so you don’t need to explicitly define these two hooks.
The source code to explore
1. Implementation of the lifecycle registration function
// 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)
}
Copy the code
2. Hook injection
export const createHook = <T extends Function = (a)= >any>( lifecycle: LifecycleHooks ) => (hook: T, target: ComponentInternalInstance | null = currentInstance) => // post-create lifecycle registrations are noops during SSR ! isInSSRComponentSetup && injectHook(lifecycle, hook, target)Copy the code
3. Process the function body
export function injectHook(
type: LifecycleHooks,
hook: Function& { __weh? :Function },
target: ComponentInternalInstance | null = currentInstance,
prepend: boolean = false
) :Function | undefined {
if (target) {
// Maintain a stack of functions that need to be executed. In addition to the developer's custom hooks, the framework itself has a lot of code that needs to be executed within each hook.
const hooks = target[type] || (target[type] = [])
/ / encapsulates the hooks
const wrappedHook =
hook.__weh ||
(hook.__weh = (. args: unknown[]) = > {
if (target.isUnmounted) {
return
}
// Suspend collection dependencies to prevent repeated collection
pauseTracking()
setCurrentInstance(target)
const res = callWithAsyncErrorHandling(hook, target, type, args)
setCurrentInstance(null)
// Resume collection
resetTracking()
return res
})
// Whether the header is pushed
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.`
: ` `))}}Copy the code
4. Register
// packages/runtime-core/src/componentOptions.ts
export function applyOptions(
instance: ComponentInternalInstance,
options: ComponentOptions,
deferredData: DataFn[] = [],
deferredWatch: ComponentWatchOptions[] = [],
deferredProvide: (Data | Function)[] = [],
asMixin: boolean = false
) {
// Retrieve the hook function from the configuration
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
/ /... Omit irrelevant code
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))
}
}
Copy the code
5. Perform
Finally, the function stack is emptied at the appropriate time during 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) {// Mount process
let vnodeHook: VNodeHook | null | undefined
const { el, props } = initialVNode
const { bm, m, parent } = instance
// beforeMount hook
if (bm) {
invokeArrayFns(bm)
}
/ /... Omit irrelevant code
// 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 {
// Update the process
let { next, bu, u, parent, vnode } = instance
let originNext = next
let vnodeHook: VNodeHook | null | undefined
/ /... omit
// 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
) = > {
/ /... omit
const { bum, effects, update, subTree, um } = instance
// beforeUnmount hook
if (bum) {
invokeArrayFns(bum)
}
// unmounted hook
if (um) {
queuePostRenderEffect(um, parentSuspense)
}
}
Copy the code
Simple and easy to implement
Based on previous work, simply implement onBeforeMount and onMounted hooks
The online demo