[fix] call `on_destroy` if unmounted called immediately before `on_mount` (#7860)

* call on_destroy if unmounted called immediately before on_mount

* feat: review changes
pull/7819/head
Tan Li Hau 2 years ago committed by GitHub
parent 57541e6abc
commit 81d4dbad99
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -3,43 +3,7 @@ import { current_component, set_current_component } from './lifecycle';
import { blank_object, is_empty, is_function, run, run_all, noop } from './utils';
import { children, detach, start_hydrating, end_hydrating } from './dom';
import { transition_in } from './transitions';
/**
* INTERNAL, DO NOT USE. Code may change at any time.
*/
export interface Fragment {
key: string | null;
first: null;
/* create */ c: () => void;
/* claim */ l: (nodes: any) => void;
/* hydrate */ h: () => void;
/* mount */ m: (target: HTMLElement, anchor: any) => void;
/* update */ p: (ctx: T$$['ctx'], dirty: T$$['dirty']) => void;
/* measure */ r: () => void;
/* fix */ f: () => void;
/* animate */ a: () => void;
/* intro */ i: (local: any) => void;
/* outro */ o: (local: any) => void;
/* destroy */ d: (detaching: 0 | 1) => void;
}
interface T$$ {
dirty: number[];
ctx: any[];
bound: any;
update: () => void;
callbacks: any;
after_update: any[];
props: Record<string, 0 | string>;
fragment: null | false | Fragment;
not_equal: any;
before_update: any[];
context: Map<any, any>;
on_mount: any[];
on_destroy: any[];
skip_bound: boolean;
on_disconnect: any[];
root:Element | ShadowRoot
}
import { T$$ } from './types';
export function bind(component, name, callback) {
const index = component.$$.props[name];
@ -58,7 +22,7 @@ export function claim_component(block, parent_nodes) {
}
export function mount_component(component, target, anchor, customElement) {
const { fragment, on_mount, on_destroy, after_update } = component.$$;
const { fragment, after_update } = component.$$;
fragment && fragment.m(target, anchor);
@ -66,9 +30,12 @@ export function mount_component(component, target, anchor, customElement) {
// onMount happens before the initial afterUpdate
add_render_callback(() => {
const new_on_destroy = on_mount.map(run).filter(is_function);
if (on_destroy) {
on_destroy.push(...new_on_destroy);
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

@ -2,11 +2,36 @@ import { is_promise } from './utils';
import { check_outros, group_outros, transition_in, transition_out } from './transitions';
import { flush } from './scheduler';
import { get_current_component, set_current_component } from './lifecycle';
import { Fragment, FragmentFactory } from './types';
interface PromiseInfo<T> {
ctx: null | any;
// unique object instance as a key to compare different promises
token: {},
hasCatch: boolean,
pending: FragmentFactory,
then: FragmentFactory,
catch: FragmentFactory,
// ctx index for resolved value and rejected error
value: number,
error: number,
// resolved value or rejected error
resolved?: T,
// the current factory function for creating the fragment
current: FragmentFactory | null,
// the current fragment
block: Fragment | null,
// tuple of the pending, then, catch fragment
blocks: [null | Fragment, null | Fragment, null | Fragment];
// DOM elements to mount and anchor on for the {#await} block
mount: () => HTMLElement;
anchor: HTMLElement;
}
export function handle_promise(promise, info) {
export function handle_promise<T>(promise: Promise<T>, info: PromiseInfo<T>) {
const token = info.token = {};
function update(type, index, key?, value?) {
function update(type: FragmentFactory, index: 0 | 1 | 2, key?: number, value?) {
if (info.token !== token) return;
info.resolved = value;

@ -5,7 +5,7 @@ import { create_rule, delete_rule } from './style_manager';
import { custom_event } from './dom';
import { add_render_callback } from './scheduler';
import { TransitionConfig } from '../transition';
import { Fragment } from './Component';
import { Fragment } from './types';
let promise: Promise<void> | null;
type INTRO = 1;

@ -0,0 +1,39 @@
/**
* INTERNAL, DO NOT USE. Code may change at any time.
*/
export interface Fragment {
key: string | null;
first: null;
/* create */ c: () => void;
/* claim */ l: (nodes: any) => void;
/* hydrate */ h: () => void;
/* mount */ m: (target: HTMLElement, anchor: any) => void;
/* update */ p: (ctx: T$$['ctx'], dirty: T$$['dirty']) => void;
/* measure */ r: () => void;
/* fix */ f: () => void;
/* animate */ a: () => void;
/* intro */ i: (local: any) => void;
/* outro */ o: (local: any) => void;
/* destroy */ d: (detaching: 0 | 1) => void;
}
export type FragmentFactory = (ctx: any) => Fragment;
export interface T$$ {
dirty: number[];
ctx: any[];
bound: any;
update: () => void;
callbacks: any;
after_update: any[];
props: Record<string, 0 | string>;
fragment: null | false | Fragment;
not_equal: any;
before_update: any[];
context: Map<any, any>;
on_mount: any[];
on_destroy: any[];
skip_bound: boolean;
on_disconnect: any[];
root:Element | ShadowRoot
}

@ -0,0 +1,13 @@
<script>
import { onMount } from 'svelte';
export let logs;
export let state;
onMount(() => {
logs.push(`mount ${state}`);
return () => {
logs.push(`unmount ${state}`);
};
});
</script>
{state}

@ -0,0 +1,9 @@
export default {
html: 'Loading...',
async test({ assert, component, target }) {
await component.test();
assert.htmlEqual(target.innerHTML, '1');
assert.deepEqual(component.logs, ['mount 0', 'unmount 0', 'mount 1']);
}
};

@ -0,0 +1,36 @@
<script>
import { tick } from 'svelte';
import Component from './Component.svelte';
let promise;
let resolve;
let value = 0;
export let logs = [];
async function new_promise() {
promise = new Promise(r => {
resolve = r;
});
}
async function resolve_promise() {
await Promise.resolve();
resolve(value++);
}
export async function test() {
resolve_promise();
await Promise.resolve();
new_promise();
resolve_promise();
return tick();
}
new_promise();
</script>
{#await promise}
Loading...
{:then state}
<Component {state} {logs} />
{/await}
Loading…
Cancel
Save