Skip to content

Commit 5a88514

Browse files
authored
[fix] bind:this during onMount in manually-created component (#6920)
1 parent 683c39a commit 5a88514

File tree

4 files changed

+65
-7
lines changed

4 files changed

+65
-7
lines changed

src/runtime/internal/scheduler.ts

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { run_all } from './utils';
2-
import { set_current_component } from './lifecycle';
2+
import { current_component, set_current_component } from './lifecycle';
33

44
export const dirty_components = [];
55
export const intros = { enabled: false };
@@ -31,23 +31,42 @@ export function add_flush_callback(fn) {
3131
flush_callbacks.push(fn);
3232
}
3333

34-
let flushing = false;
34+
// flush() calls callbacks in this order:
35+
// 1. All beforeUpdate callbacks, in order: parents before children
36+
// 2. All bind:this callbacks, in reverse order: children before parents.
37+
// 3. All afterUpdate callbacks, in order: parents before children. EXCEPT
38+
// for afterUpdates called during the initial onMount, which are called in
39+
// reverse order: children before parents.
40+
// Since callbacks might update component values, which could trigger another
41+
// call to flush(), the following steps guard against this:
42+
// 1. During beforeUpdate, any updated components will be added to the
43+
// dirty_components array and will cause a reentrant call to flush(). Because
44+
// the flush index is kept outside the function, the reentrant call will pick
45+
// up where the earlier call left off and go through all dirty components. The
46+
// current_component value is saved and restored so that the reentrant call will
47+
// not interfere with the "parent" flush() call.
48+
// 2. bind:this callbacks cannot trigger new flush() calls.
49+
// 3. During afterUpdate, any updated components will NOT have their afterUpdate
50+
// callback called a second time; the seen_callbacks set, outside the flush()
51+
// function, guarantees this behavior.
3552
const seen_callbacks = new Set();
53+
let flushidx = 0; // Do *not* move this inside the flush() function
3654
export function flush() {
37-
if (flushing) return;
38-
flushing = true;
55+
const saved_component = current_component;
3956

4057
do {
4158
// first, call beforeUpdate functions
4259
// and update components
43-
for (let i = 0; i < dirty_components.length; i += 1) {
44-
const component = dirty_components[i];
60+
while (flushidx < dirty_components.length) {
61+
const component = dirty_components[flushidx];
62+
flushidx++;
4563
set_current_component(component);
4664
update(component.$$);
4765
}
4866
set_current_component(null);
4967

5068
dirty_components.length = 0;
69+
flushidx = 0;
5170

5271
while (binding_callbacks.length) binding_callbacks.pop()();
5372

@@ -73,8 +92,8 @@ export function flush() {
7392
}
7493

7594
update_scheduled = false;
76-
flushing = false;
7795
seen_callbacks.clear();
96+
set_current_component(saved_component);
7897
}
7998

8099
function update($$) {
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<script>
2+
import { onMount } from 'svelte';
3+
4+
let element;
5+
let bound = false;
6+
onMount(() => {
7+
if (element) bound = true;
8+
});
9+
10+
</script>
11+
12+
<div bind:this={element}></div>
13+
<p>
14+
Bound? {bound}
15+
</p>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export default {
2+
async test({ assert, target }) {
3+
assert.htmlEqual(target.innerHTML, `
4+
<div id="target"><div></div>
5+
<p>
6+
Bound? true
7+
</p>
8+
</div>
9+
`);
10+
}
11+
};
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<script>
2+
import Mount from './Mount.svelte';
3+
import { onMount } from 'svelte';
4+
5+
onMount(() => {
6+
const component = new Mount({
7+
target: document.querySelector('#target'),
8+
props: {},
9+
});
10+
});
11+
</script>
12+
13+
<div id="target" />

0 commit comments

Comments
 (0)