Svelte Series-6: Lifecycle

Svelte Series-6: Lifecycle

Each component has a life cycle from creation to destruction. At different life cycle stages, a component provides a method to the outside world, allowing the developer who calls the component to control the component more flexibly at each life cycle stage. Such methods are life hook functions.

Svelte’s lifecycle hooks are:

onMount
beforeUpdate
afterUpdate
onDestroy
tick

Call Timing

If server-side rendering (SSR) is used, the rest of the lifecycle functions will not be executed during SSR execution except for onDestroy.

In Svelte, lifecycle functions can only be written when the component is initialized to bind callbacks to instances of the component. Do not place lifecycle functions in asynchronous methods such as setTimeout and setInterval.

Although we need to ensure that lifecycle functions are called when the component is initialized, it doesn’t matter where they are called from. For example, we can put the lifecycle in a method:

// app.js
import { onMount,onDestroy } from svelte;

export const startInterval = () => {
let timer = null;
let count = 0;

onMount(() => {
console.log(onMount);
timer = setInterval(() => {
count++;
console.log(count);
}, 1000)
});

onDestroy(() => {
console.log(onDestroy);
if (timer) {
clearInterval(timer);
}
})
}

<script>
import { startInterval } from ./app.js;

startInterval();
</script>

<div>App</div>

In this case, the lifecycle can still ensure that it is called when the component is initialized.

onMount

A callback that is executed immediately after the component is mounted to the DOM.

onMount(callback: () => void)

onMount(callback: () => () => void)

onMount receives a callback function as an argument, and if a function is returned within the callback function, it is called when the component is unloaded.

We can take a look at the source code in the Svelte project, file path ispackages/svelte/src/runtime/internal/Component.js:

/** @returns {void} */
export function mount_component(component, target, anchor) {
const { fragment, after_update } = component.$$;
fragment && fragment.m(target, anchor);
// onMount happens before the initial afterUpdate
add_render_callback(() => {
const new_on_destroy = component.$$.on_mount.map(run).filter(is_function);
// if the component was destroyed immediately
// it will update the `$$.on_destroy` reference to `null`.
// the destructured on_destroy may still reference to the old array
if (component.$$.on_destroy) {
component.$$.on_destroy.push(…new_on_destroy);
} else {
// Edge case – component was destroyed immediately,
// most likely as a result of a binding initialising
run_all(new_on_destroy);
}
component.$$.on_mount = [];
});
after_update.forEach(add_render_callback);
}

We can see that it will execute onMount method and gets a value, and then it will determine whether there is a declaration in the component onDestroy lifecycle hook, add to onDestroy array if yes, then execute this return value after onDestroy, if not, execute this return value directly. In React, useEffect hook has the same function.

useEffect(() => {
return () => {}
}, []);

We demonstrate a Svelte Example of the return value of onMount:

<script>
// Child.svelre
import { onMount } from svelte;

onMount(() => {
console.log(child mount);
return () => {
console.log(child destroy 1);
}
})
</script>

<div>child</div>

<script>
// Father.svelte
import Child from ./Child.svelte;
let count = 0;

const updateCount = () => {
count++;
}
</script>

<button on:click={updateCount}>add</button>
{#if count == 0}
<Child />
{/if}

In this example, we have a variable count on the parent page, which controls the implicit value of the child component by changing the count. Executable the code, the console will output in sequence: child mount -> child destroy 1.

Then we add onDestroy hook:

<script>
import { onMount, onDestroy } from svelte;

onMount(() => {
console.log(child mount);
return () => {
console.log(child destroy 1);
}
})

onDestroy(() => {
console.log(child destroy 2);
})
</script>

<div>child</div>

Executable the code, the console will print out: child mount -> child destroy 2 -> child destroy 1.

For sibling components, onMount is executed from top to bottom according to the order of component calls. For parent and child components, onMount of the child component is executed before onMount of the parent component is executed, that is, from inside to outside.

<script>
import { onMount } from svelte;
import Child from ./Child.svelte;
import Child2 from ./Child2.svelte;

onMount(() => {
console.log(fahter mount);
});
</script>

<Child />
<Child2 />

We define three components: a parent component and two child components. onMount is called separately within the three components. After execution, we can see: child mount 1 -> child mount 2 -> father mount.

beforeUpdate

Execute before the DOM update, the first callback runs on onMount before initialization.

beforeUpdate(callback: () => void)

An example here:

<script>
import { beforeUpdate } from svelte;

let count = 0;
let str = “”;

const updateData = () => {
count++;
setTimeout(() => {
str += s;
}, 1000);
};

beforeUpdate(() => {
console.log(before update, count, str);
});
</script>

<button on:click={updateData}>update</button>
<span>{count}</span><span>{str}</span>

After loading the page, we will first see the console print out before update 0. Click the button to update and see before update 1 printed out, 1s later see before update 1 s printed out.

It is important to note that if components are present simultaneously beforeUpdate and
onMount for the first time. The beforeUpdate callback will be execute before onMount. If we call onMount hook in the above code, and then perform the same steps, we will see:

afterUpdate

Callback executed after the component is rendered.

afterUpdate(callback: () => void)

For sibling components, beforeUpdate and afterUpdate are still executed from top to bottom in the order of component calls. For parent and child components, beforeUpdate of the parent component is executed first, and then beforeUpdate of the child component is executed.

When the beforeUpdate of the child component is completed, the afterUpdate of the parent component is executed, and the afterUpdate of the child component is finally executed.

<script>
import { onMount, onDestroy, beforeUpdate, afterUpdate } from svelte;
import Child from ./Child.svelte;
import Child2 from ./Child2.svelte;
let count = 0;

const updateCount = () => {
count++;
};

beforeUpdate(() => {
console.log(fater beforeupdate);
});

onMount(() => {
console.log(father mount);
});

afterUpdate(() => {
console.log(father afterupdate);
});

onDestroy(() => {
console.log(father destroy);
});
</script>

<button on:click={updateCount}>add</button>
{#if count <= 1}
<Child {count} />
<Child2 {count} />
{/if}

The content of the child component is as follows. Child1.svelte and Child2.svelte only have different serial numbers of the output content, and nothing else is different.

<script>
// Child.svelte
import { onMount, onDestroy, beforeUpdate, afterUpdate } from svelte;

export let count;

beforeUpdate(() => {
console.log(child beforeupdate 1)
});

onMount(() => {
console.log(child mount 1);
});

afterUpdate(() => {
console.log(child afterupdate 1);
});

onDestroy(() => {
console.log(child destroy 1);
});
</script>

<div>child</div>
{count}

At the time of initial execution:

After clicking the button to update:

onDestroy

Callbacks to run after a component is destroyed:

onDestroy(callback: () => void)

Between sibling components, onDestroy is still from top to bottom according to the calling order. The parent and child components are from outside to inside, and the parent component executes onDestroy firstly, and then execute the child component’s
onDestroy.

The function returned by onMount will be executed when the component is destroyed
adn after executes onDestroy.

tick

The tick function is unlike other lifecycle hooks in that we can call it at any time without waiting for the component to initialize for the first time. It returns a
Promise, which resolves immediately when any state state changes.

promise: Promise = tick()

In Svelte, when a state state changes, the DOM is not updated immediately, but will wait until the next microtask to update. During the waiting period, it will continue to listen for other state state changes, and then update the DOM uniformly in this microtask, which can reduce some unnecessary work and allow the browser to process these things in batches more efficiently.

We may encounter this problem during development: a state in the component is updated, but the DOM is not updated, and I just want to get the value of dom, then the tick function can come in handy! (Yes, it can be regarded as $nextTick () in Vue)

<script>
let count = 0;

const addCount = () => {
count++;
let countDom = document.querySelector(#count);
if (countDom) {
console.log(count text, countDom.innerHTML);
}
}
</script>

<button on:click={addCount}>add</button>

<span id=“count”>{count}</span>

We can see that even if the page has been updated, the value retrieved by the DOM operation is still old data.

To solve the above problems, we can:

<script>
import { tick } from svelte;
let count = 0;

const addCount = async () => {
count++;
await tick();
let countDom = document.querySelector(#count);
if (countDom) {
console.log(count text, countDom.innerHTML);
}
}
</script>

<button on:click={addCount}>add</button>

<span id=“count”>{count}</span>

And when we use tick, we can get the latest value.

Summary

In this chapter, we have learned:

The Role of the Svelte Lifecycle Function
The execution order of each lifecycle function when the parent and child components are rendered
The role of tick

Please follow and like us:
Pin Share