preface
Version: 3.0.2
Vue3 life cycle through the source code is described, including usage, use instructions, source code introduction, etc.
1. Opening a picture
What is the life cycle?
I think it’s quite understandable.
You call different hooks at different times, just like people do different things at different ages; But some hooks need to trigger under special circumstances, just like people do, and what happens when they do something.
Ii. Usage of each life cycle
- 1, page initialization, direct trigger
Related hooks are beforeCreate, created, beforeMount, renderTracked, mounted
Usage:
Vue.createApp({
// Data is not available at this time
beforeCreate() {
console.log("beforeCreate");
},
// Data is available, DOM is not available
created() {
console.log("created");
},
// After this hook and before the mounted lifecycle hook, the render function is first called
beforeMount() {
console.log("beforeMount");
},
// triggered when the page has value operations (e.g. binding data, e.g. interpolation syntax {{count}})
renderTracked({ type, key, target, effect }) {
console.log("renderTracked ----", { type, key, target, effect });
},
// Triggered after the page is mounted
mounted() {
console.log("mounted");
}
}).mount("#app");
Copy the code
Output:
beforeCreate
created
beforeMount
renderTracked ---- {type: "get", key: "count", target: {... }, effect: f} mountedCopy the code
Vue3.x added a life cycle renderTracked description
Official explanation: Called when tracing the virtual DOM to re-render (also called when initializing the render). The hook receives a Debugger event as an argument. This event tells you which action tracks the component and the target object and key for that action.
In simple terms, this operation is triggered when reactive data (values) are bound to the page.
Take a chestnut
<div id="app">
<div>{{ count }}</div>
<button @click="addCount">Add 1</button>
</div>
Copy the code
Vue.createApp({
methods: {
addCount() {
this.count += 1; }},// Each time you render, the 'renderTracked' hook is fired.
renderTracked(e) {
console.log("renderTracked ----", e);
}
}).mount("#app");
Copy the code
Output:
renderTracked ---- {type: "get", key: "count", target: {... }, effect: f}Copy the code
debugger event
instructions
Type: Operation type, including get, has, iterate.
Key: a key used to manipulate data.
Target: indicates a responsive object, such as data, ref, and computed.
Effect: The data type is Function, and the English word means to evoke and execute. The effect method rerenders the view.
- 2. Triggered after data changes
Related hooks: renderTriggered, beforeUpdate, renderTracked, updated
Usage:
Vue.createApp({
// change data (e.g. set)
renderTriggered(e) {
console.log("renderTriggered ----", e);
},
/*--------- called after the data has changed and before the DOM is updated. -- -- -- -- -- -- -- -- -- - * /
beforeUpdate() {
console.log("beforeUpdate");
},
// read data (e.g.
renderTracked(e) {
console.log("renderTracked ----", e);
},
/*--------- is called after DOM is updated. Note: Updated does not guarantee that all child components have also been rerendered ---------*/
updated() {
console.log("updated");
}
}).mount("#app");
Copy the code
Output:
renderTriggered ---- {target: {... }, key:"count", type: "set", newValue: 2, effect: f, oldTarget: undefined, oldValue: 1} beforeUpdate renderTracked ---- {target: {... }, type:"get", key: "count", effect: f}
update
Copy the code
Vue3.x added the life cycle renderTriggered description
Official explanation: called when the virtual DOM rerendering is triggered. Receive a Debugger event as a parameter. This event tells you what action triggered the rerender, as well as the target object and key for that action.
Simple understanding: Something was done that caused the page to be rerendered.
Take a chestnut
<div id="app">
<div>{{ count }}</div>
<button @click="addCount">Add 1</button>
</div>
Copy the code
Vue.createApp({
methods: {
addCount() {
this.count += 1; }},// Every time reactive data is modified, the 'renderTriggered' hook is triggered
renderTriggered(e) {
console.log("renderTriggered ----", e);
}
}).mount("#app");
Copy the code
Output:
renderTriggered ---- {target: {... }, key:"count", type: "set", newValue: 2, effect: f, oldTarget: undefined, oldValue: 1}
Copy the code
debugger event
instructions
Type: Indicates the operation type, including set, Add, clear, and delete.
Key: a key used to manipulate data. E.g. the count used above.
Target: indicates a responsive object, such as data, ref, and computed.
Effect: The data type is Function. The English word means to evoke, perform. The effect method rerenders the view.
NewValue: indicates the newValue.
OldValue: indicates the oldValue.
OldTarget: Old reactive object.
- 3. Triggered when the component is uninstalled
Hooks involved: beforeUnmount and unmounted
A simulation
V-if is used to simulate the destruction of child components.
<div id="app">
<! -- Subcomponent -->
<child v-if="flag"></child>
<button @click="unload">Unload child components</button>
</div>
Copy the code
First define a child component, then reference it in the page and destroy the child component by clicking the Uninstall child component button.
const { defineComponent } = Vue;
const child = defineComponent({
data() {
return {
title: "I am a child component"}},template: `<div>{{ title }}</div>`.// Called before the component instance is unloaded, at which point the instance is still available.
beforeUnmount() {
console.log("beforeUnmount");
},
// called after the component instance is unloaded.
unmounted() {
console.log("unmounted"); }}); Vue.createApp({components: {
child
},
data: () = > {
return {
flag: true}},methods: {
unload() {
this.flag = false;
}
}
}).mount("#app");
Copy the code
Click the button to unload the child component and re-render the page. When you open the console, it will print:
beforeUnmount
unmounted
Copy the code
- Catch the wrong hook
Hooks involved: errorCaptured
Official explanation: Called when catching an error from a descendant component. The hook receives three parameters: the error object, the component instance that sent the error, and a string containing information about the source of the error. This hook can return false to prevent further propagation of the error.
A simulation
Reference in the page.
<div id="app">
<! -- Parent component -->
<parent v-if="flag"></parent>
</div>
Copy the code
You define a child component and a parent component, reference the parent component in the page, and then reference the child component in the parent.
const { defineComponent } = Vue;
const child = definedComponent({
data() {
return {
title: "I am a child component"}},template: `<div>{{ title }}</div>`.setup() {
// This method is undefined. Calling it directly will raise an error
childSetupError();
return{}; }});const parent = defineComponent({
components: {
child
},
data() {
return {
title: "Render subcomponent:"}},setup() {
// This method is also undefined. Calling it directly will raise an error
parentSetupError();
return {};
},
template: `
{{ title }}
`.errorCaputed(err, instance, info) {
console.log("errorCaptured", err, instance, info);
return false; }});const app = Vue.createApp({
components: {
parent
},
errorCaptured(err, instance, info) {
console.log("errorCaptured", err, instance, info); }}); app.config.errorHandler =(err, vm, info) = > {
console.log("configErrorHandler", err, vm, info);
};
app.mount("#app");
Copy the code
errorCaptured
Parameters that
Err: Error Error object; E.g.ferenceerror: parentSetupError is not defined.
Instance: indicates a component instance.
Info: error information captured. e.g.setup function
errorCaptured
Returns afalse
The understanding of the
Error propagation rules are top-down. By default, if the global config.errorHandler is defined, all errors will eventually be passed to it. You can prevent errors from being passed up by explicitly using return false.
To illustrate:
ErrorCaptured hooks return false, and child childSetupError errors are only passed to the parent component. The parent component’s parentSetupError error is passed to both the Vue. CreateApp initialization Vue instance and app.config.errorHandler.
So, the order of output is:
errorCaptured ReferenceError: parentSetupError is not defined
configErrorHandler ReferenceError: parentSetupError is not defined
parentCaptured ReferenceError: childSetupError is not defined
Copy the code
Three, source code introduction
How are lifecycle hooks implemented in Vue3 source code? Understand the execution of the lifecycle step by step through the introduction of each of the following functions. Because some functions involve a lot of code, I will present only the important parts of the code related to the lifecycle.
Let’s start with the applyOptions function.
The applyOptions function creates the options passed by the application programmer, that is, createApp(Options) creates the options passed by a Vue instance
function applyOptions(instance, options, deferredData = [], deferredWatch = [], deferredProvide = [], asMixin = false) {
// ...
// -------------------beforeCreate Life cycle
callSyncHook('beforeCreate'."bc" /* BEFORE_CREATE */, options, instance, globalMixins);
// ...
// dataOptions => data
if (dataOptions) {
// Parse the data object and turn it into a responsive object
resolveData(instance, dataOptions, publicThis);
}
// ...
// -------------------created lifecycle
// At this point, data is ready to use
callSyncHook('created'."c" /* CREATED */, options, instance, globalMixins);
// ------------------ register hooks
if (beforeMount) {
// deforeMount
onBeforeMount(beforeMount.bind(publicThis));
}
if (mounted) {
// mounted
onMounted(mounted.bind(publicThis));
}
if (beforeUpdate) {
// beforeUpdate
onBeforeUpdate(beforeUpdate.bind(publicThis));
}
if (updated) {
// updated
onUpdated(updated.bind(publicThis));
}
if (errorCaptured) {
// errorCaptured
onErrorCaptured(errorCaptured.bind(publicThis));
}
if (renderTracked) {
// renderTracked
onRenderTracked(renderTracked.bind(publicThis));
}
if (renderTriggered) {
// renderTriggered
onRenderTriggered(renderTriggered.bind(publicThis));
}
// This hook has been removed
if (beforeDestroy) {
BeforeDestory was renamed to beforeUnmount
warn(`\`beforeDestroy\` has been renamed to \`beforeUnmount\`.`);
}
if (beforeUnmount) {
// beforeUnmount
onBeforeUnmount(beforeUnmount.bind(publicThis));
}
// This hook has been removed
if (destroyed) {
// DeStoryed was renamed to unmounted
warn(`\`destroyed\` has been renamed to \`unmounted\`.`);
}
if (unmounted) {
// unmountedonUnmounted(unmounted.bind(publicThis)); }}Copy the code
In the applyOptions function, you can see that Vue3 uses callSyncHook to execute our defined lifecycle hooks, such as beforeCreate. Let’s look at the callSyncHook function.
// Execute hook synchronously
function callSyncHook(name, type, options, instance, globalMixins) {
// Trigger globally defined mixins
callHookFromMixins(name, type, globalMixins, instance);
const { extends: base, mixins } = options;
if (base) {
// Triggers a hook in extends
callHookFromExtends(name, type, base, instance);
}
if (mixins) {
// Trigger our custom mixins
callHookFromMixins(name, type, mixins, instance);
}
// Custom hook
// e.g. beforeCreate, created
const selfHook = options[name];
if(selfHook) { callWithAsyncErrorHandling(selfHook.bind(instance.proxy), instance, type); }}Copy the code
If the extends extends exists, or if the mixins exist, then the lifecycle hooks in each of the two are triggered first. Finally, we look for the lifecycle hook functions we defined in the component.
Let’s take a look at callWithAsyncErrorHandling function.
// Execute the callback with a try catch wrap to handle the error message
function callWithErrorHandling(fn, instance, type, args) {
let res;
try{ res = args ? fn(... args) : fn(); }catch (err) {
handleError(err, instance, type);
}
return res;
}
function callWithAsyncErrorHandling(fn, instance, type, args) {
// If the fn passed in is a Function type
if (isFunction(fn)) {
/ / execution fn
const res = callWithErrorHandling(fn, instance, type, args);
// If there is a return value of type PROMISE, add a catch to the return value to catch the error message
if (res && isPromise(res)) {
res.catch(err= > {
handleError(err, instance, type);
});
}
return res;
}
// If the hook is an array type, each item in the array is iterated and the array result is returned
const values = [];
for (let i = 0; i < fn.length; i++) {
values.push(callWithAsyncErrorHandling(fn[i], instance, type, args));
}
return values;
}
Copy the code
In the applyOptions function, two hooks are created (beforeCreate, created) and the rest are injected and created.
injection
/ / create a hook
const createHook = (lifecycle) = > (hook, target = currentInstance) = >! isInSSRComponentSetup && injectHook(lifecycle, hook, target);const onBeforeMount = createHook("bm" /* BEFORE_MOUNT */);
const onMounted = createHook("m" /* MOUNTED */);
const onBeforeUpdate = createHook("bu" /* BEFORE_UPDATE */);
const onUpdated = createHook("u" /* UPDATED */);
const onBeforeUnmount = createHook("bum" /* BEFORE_UNMOUNT */);
const onUnmounted = createHook("um" /* UNMOUNTED */);
const onRenderTriggered = createHook("rtg" /* RENDER_TRIGGERED */);
const onRenderTracked = createHook("rtc" /* RENDER_TRACKED */);
const onErrorCaptured = (hook, target = currentInstance) = > {
injectHook("ec" /* ERROR_CAPTURED */, hook, target);
};
// Inject hook, target is instance
function injectHook(type, hook, target = currentInstance, prepend = false) {
if (target) {
// hook on Vue instance, e.g. instance.bm = [fn];
const hooks = target[type] || (target[type] = []);
const wrappedHook = hook.__weh ||
(hook.__weh = (. args) = > {
if (target.isUnmounted) {
return;
}
pauseTracking();
setCurrentInstance(target);
// Triggers the injected hook
const res = callWithAsyncErrorHandling(hook, target, type, args);
setCurrentInstance(null);
resetTracking();
return res;
});
if (prepend) {
// Pre-injection
hooks.unshift(wrappedHook);
}
else {
hooks.push(wrappedHook);
}
return wrappedHook;
}
else {
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().` +
(` If you are using async setup(), make sure to register lifecycle ` +
`hooks before the first await statement.`)); }}Copy the code
The trigger
The synchronous trigger hook is invoked through the invokeArrayFns method.
const invokeArrayFns = (fns, arg) = > {
// Iterate over the number group, executing each item
for (let i = 0; i < fns.length; i++) { fns[i](arg); }};Copy the code
Take a chestnut
Trigger the beforeMount hook when rendering a component.
// In injectHook, bm and m have been added to instance, and bm and m are arrays
const { bm, m, parent } = instance;
if (bm) {
// ---------------beforeMount Life cycle
invokeArrayFns(bm);
}
Copy the code
The hooks that are triggered using synchronization are beforeMount, beforeUpdate, beforeUnmount, beforeUnmount, renderTracked, renderTriggered, errorCaptured
Triggers hooks asynchronously, using the queuePostRenderEffect method to clean up the hook function in the queue.
Update task queues to support suspense components
function queueEffectWithSuspense(fn, suspense) {
if (suspense && suspense.pendingBranch) {
if(isArray(fn)) { suspense.effects.push(... fn); }else{ suspense.effects.push(fn); }}else{ queuePostFlushCb(fn); }}// Refresh the callback function for the post-task queue
function queuePostFlushCb(cb) {
queueCb(cb, activePostFlushCbs, pendingPostFlushCbs, postFlushIndex);
}
// Add callback function to wait queue and refresh callback queue
function queueCb(cb, activeQueue, pendingQueue, index) {
if(! isArray(cb)) {if(! activeQueue || ! activeQueue.includes(cb, cb.allowRecurse ? index +1: index)) { pendingQueue.push(cb); }}else{ pendingQueue.push(... cb); }// Refresh the task
queueFlush();
}
const queuePostRenderEffect = queueEffectWithSuspense;
Copy the code
Use:
const { m } = instance;
if (m) {
// ----------------- Mounted Life cycle
queuePostRenderEffect(m, parentSuspense);
}
Copy the code
The following hooks that use asynchronous triggering are Mounted and updated
The queueFlush function flushes the task queue, that is, traverses all hooks in the queue and executes. There is a lot of code involved in asynchronous hook triggering that I won’t explain here. If you want to know more, you can click on the appendix at the end of this article, which is the Vue3 source code comment I wrote.
3 things to watch
1. Give the blogger a “like” if the post has helped you.
If you think the article is good, you can move your little hands and collect it.
3, if you want to see more source code details, you can add follow the blogger.
Appendix: github.com/fanqiewa/vu…