onMount 被调用两次
onMount is being called twice
我有一个布局页面,每个组件都继承自它。我的问题是每个组件都调用了 onMount 函数两次。它会产生很多问题,因为我的 onMount 函数包含对 API 的调用。这意味着对 Api 的调用增加了一倍。很长一段时间,我都不知道为什么会这样。最近我注意到布局组件有一个触发转换的包装器。当我删除过渡时,onMount 按预期被调用了一次。另一方面,我不想因为页面看起来很糟糕而失去转换。如何解决这个问题?
PageTransitions.svelte:
<script>
import { fly } from "svelte/transition";
export let refresh = "";
</script>
{#key refresh}
<div in:fly={{ y: -50, duration: 250, delay: 300 }} out:fly={{ y: -50, duration: 250 }} class="flex flex-1">
<slot>Zawartość</slot>
</div>
{/key}
layout.svelte:
<script>
import { googleMap } from "./../stores.js";
import { page } from "$app/stores";
import Footer from "$lib/Footer.svelte";
import Header from "$lib/Header.svelte";
import PageTransitions from "$lib/PageTransitions.svelte";
import Notifications from "svelte-notifications";
import "../app.css";
import { GOOGLE_API_KEY } from "$lib/constants.js";
import { setContext } from "svelte";
import { writable } from "svelte/store";
const url = `https://maps.googleapis.com/maps/api/js?key=${GOOGLE_API_KEY}&libraries=places,directions`;
let isSideMenuOpen = setContext("isSideMenuOpen", writable(false));
$: loaded = $googleMap.loaded;
const onLoad = () => {
$googleMap.loaded = true;
console.log("Google Maps SDK loaded…", window.google);
};
</script>
<svelte:head>
{#if !loaded}
<script
src={url}
type="application/javascript"
defer
async
on:load={onLoad}></script>
{/if}
</svelte:head>
<Notifications>
<div
class="h-screen flex flex-col bg-gray-50 dark:bg-gray-900"
class:overflow-hidden={isSideMenuOpen}
>
<Header />
<PageTransitions refresh={$page.path}>
<slot>Strona</slot>
</PageTransitions>
<Footer />
</div>
</Notifications>
modules.svelte(示例组件):
<script context="module">
export const ssr = false;
export const prerender = true;
export async function load({ session }) {
if (!session.authenticated) {
return {
status: 302,
redirect: "/auth/login",
};
}
return {
props: {
authenticated: session.authenticated,
token: session.token,
user: session.user,
},
};
}
</script>
<script>
import { slide } from 'svelte/transition';
import GenericTable from "$lib/GenericTable.svelte";
import { profile, modules } from "../../stores";
import { getModuleDataURL, getModuleTypeDataURL, get, del } from "$lib/api";
import GenericRow from "$lib/Rows/ModuleRow.svelte";
import { onMount } from "svelte";
import Loader from "$lib/Loader.svelte";
import { getNotificationsContext } from "svelte-notifications";
const { addNotification } = getNotificationsContext();
let loading = false;
export let authenticated;
export let token;
export let user;
const siteHeader = "Moduły";
const rootPath = "modules";
let filters = {
moduleType: null,
imei: "",
serialNumber: "",
phoneNumber: "",
isActive: null,
desc: "",
id: null,
};
const filtersChange = () => {
let filteredItems = [...$modules.items];
$modules.filters = false;
if (
filters.moduleType ||
filters.id ||
filters.desc.length > 2 ||
filters.isActive !== null ||
filters.imei.length > 2 ||
filters.serialNumber.length > 2 ||
filters.phoneNumber.length > 2
) {
$modules.filters = true;
}
if (filters.moduleType) {
filteredItems = filteredItems.filter(
(item) => item.moduleType.id === filters.moduleType.value
);
}
if (filters.isActive !== null) {
filteredItems = filteredItems.filter(
(item) => item.isActive === filters.isActive
);
}
if (filters.imei.length > 2) {
filteredItems = filteredItems.filter((item) =>
item.imei.includes(filters.imei)
);
}
if (filters.serialNumber.length > 2) {
filteredItems = filteredItems.filter((item) =>
item.serialNumber.includes(filters.serialNumber)
);
}
if (filters.phoneNumber.length > 2) {
filteredItems = filteredItems.filter((item) =>
item.phoneNumber.includes(filters.phoneNumber)
);
}
if (filters.desc.length > 2) {
filteredItems = filteredItems.filter((item) =>
item.desc ? item.desc.includes(filters.desc) : false
);
}
if (filters.id === "DESC") {
filteredItems.sort((a, b) => (a.id > b.id ? 1 : -1));
} else if (filters.id === "ASC") {
filteredItems.sort((a, b) => (a.id < b.id ? 1 : -1));
}
$modules.filteredModules = filteredItems;
};
const urlModuleType = getModuleTypeDataURL();
const urlModuleData = getModuleDataURL();
const deleteItem = async (id) => {
loading = true;
const url = getModuleDataURL() + "?id=" + id;
try {
const result = await del(url, token, false);
if (result.status) {
$modules.items = $modules.items.filter((item) => id !== item.id);
$modules.filteredModules = $modules.filteredModules.filter(
(item) => id !== item.id
);
addNotification({
text: "Usunięto moduł o id - " + id,
position: "bottom-right",
type: "success",
removeAfter: 4000,
});
} else throw result.message;
} catch (err) {
console.error(err);
addNotification({
text: err,
position: "bottom-right",
type: "danger",
removeAfter: 4000,
});
}
loading = false;
};
onMount(async () => {
loading = true;
$modules.filters = false;
$modules.filteredGroups = [];
try {
const resultModules = await get(
urlModuleData + "?$orderby=isActive desc,id",
token
);
if (resultModules.data) $modules.items = resultModules.data.items;
const resultModuleTypes = await get(urlModuleType, token);
if (resultModuleTypes.data)
$modules.moduleTypes = resultModuleTypes.data.items;
} catch (err) {
console.error(err);
addNotification({
text: "Podczas komunikacji z serwerem wystąpił błąd. Spróbuj raz jeszcze. Jeśli problem się powtórzy, skontaktuj się z administratorem aplikacji.",
position: "bottom-right",
type: "danger",
removeAfter: 4000,
});
}
loading = false;
});
let virtualListData = [];
$: virtualListData = $modules.filters
? $modules.filteredModules
: $modules.items;
let moduleTypes = [];
$: moduleTypes = $modules.moduleTypes.map((item) => ({
value: item.id,
label: item.type,
}));
const widths = ["5%", "15%", "10%", "10%", "15%", "15%", "20%", "10%"];
let listTableHead = [];
$: listTableHead = [
{
id: 1,
name: "ID",
filter: true,
type: "id",
},
{
id: 2,
name: "IMEI",
filter: true,
label: "imei",
type: "text",
placeholder: "Wpisz minimum 3 znaki",
onInput: filtersChange,
},
{
id: 3,
name: "Typ modułu",
filter: true,
label: "moduleTypes",
type: "select",
placeholder: "-",
items: moduleTypes,
select: (e) => {
filters.moduleType = e.detail;
filtersChange();
},
clear: () => {
filters.moduleType = null;
filtersChange();
},
},
{
id: 4,
name: "Aktywność",
filter: true,
label: "isActive",
type: "select",
placeholder: "-",
items: [
{ value: true, label: "Aktywny" },
{ value: false, label: "Nieaktywny" },
],
select: (e) => {
filters.isActive = e.detail.value;
filtersChange();
},
clear: () => {
filters.isActive = null;
filtersChange();
},
},
{
id: 5,
name: "Numer telefonu",
filter: true,
label: "phoneNumber",
type: "text",
placeholder: "Wpisz minimum 3 znaki",
onInput: filtersChange,
},
{
id: 6,
name: "Numer seryjny",
filter: true,
label: "serialNumber",
type: "text",
placeholder: "Wpisz minimum 3 znaki",
onInput: filtersChange,
},
{
id: 7,
name: "Opis",
filter: true,
label: "desc",
type: "text",
placeholder: "Wpisz minimum 3 znaki",
onInput: filtersChange,
},
{
id: 8,
name: "Dodaj nowy moduł",
type: "addNew",
},
];
</script>
<main class="w-full flex flex-1 overflow-y-auto bg-red-100">
<div class="p-2 overflow-y-auto flex flex-1">
{#if loading}
<Loader />
{/if}
<div
transition:slide
style="display:{loading ? 'none' : 'block'}"
class="flex flex-col flex-1 items-center justify-center"
>
<GenericTable
{siteHeader}
{filtersChange}
{deleteItem}
{virtualListData}
{widths}
{listTableHead}
{filters}
{rootPath}
{GenericRow}
/>
</div>
</div>
</main>
我尝试了什么:
- 我试图从 layout.svelte 组件中删除 PageTransitions,它有助于 onMount 功能,但页面看起来很糟糕
- 我尝试将过渡分别移动到每个组件,部分有效但并非所有地方都有效
- 试图将 onMount 函数内容移动到 Load 函数,但我无法编译
为什么会这样?
记录了一个相关问题 here(没有解决方案)。
根本原因是时间问题。
layout.svelte
更改 PageTransitions.svelte
中的 <slot/>
。
这会导致新的 slot/page 加载甚至触发 onMount。
新页面也会在屏幕上显示几毫秒,因为尚未发生转换!
插槽更改后,Svelte 会触发 $page
存储进行更新。
在您的代码中,这会导致转换更新 ({#key refresh}
),因此会再次加载 <slot/>
。
如果不修改 Svelte 本身,就无法更改 <slot/>
交换和 $page
存储集之间的时间。
此代码触发转换并且只导致一个组件挂载。
layout.svelte:
<script context="module">
export const load = async ({ url }) => ({ props: { refresh: url } });
</script>
<script>
import { googleMap } from "./../stores.js";
import Footer from "$lib/Footer.svelte";
import Header from "$lib/Header.svelte";
import PageTransitions from "$lib/PageTransitions.svelte";
import Notifications from "svelte-notifications";
import "../app.css";
import { GOOGLE_API_KEY } from "$lib/constants.js";
import { setContext } from "svelte";
import { writable } from "svelte/store";
export let refresh;
const url = `https://maps.googleapis.com/maps/api/js?key=${GOOGLE_API_KEY}&libraries=places,directions`;
let isSideMenuOpen = setContext("isSideMenuOpen", writable(false));
$: loaded = $googleMap.loaded;
const onLoad = () => {
$googleMap.loaded = true;
console.log("Google Maps SDK loaded…", window.google);
};
</script>
<svelte:head>
{#if !loaded}
<script
src={url}
type="application/javascript"
defer
async
on:load={onLoad}></script>
{/if}
</svelte:head>
<Notifications>
<div
class="h-screen flex flex-col bg-gray-50 dark:bg-gray-900"
class:overflow-hidden={isSideMenuOpen}
>
<Header />
<PageTransitions {refresh}>
<slot>Strona</slot>
</PageTransitions>
<Footer />
</div>
</Notifications>
我有一个布局页面,每个组件都继承自它。我的问题是每个组件都调用了 onMount 函数两次。它会产生很多问题,因为我的 onMount 函数包含对 API 的调用。这意味着对 Api 的调用增加了一倍。很长一段时间,我都不知道为什么会这样。最近我注意到布局组件有一个触发转换的包装器。当我删除过渡时,onMount 按预期被调用了一次。另一方面,我不想因为页面看起来很糟糕而失去转换。如何解决这个问题?
PageTransitions.svelte:
<script>
import { fly } from "svelte/transition";
export let refresh = "";
</script>
{#key refresh}
<div in:fly={{ y: -50, duration: 250, delay: 300 }} out:fly={{ y: -50, duration: 250 }} class="flex flex-1">
<slot>Zawartość</slot>
</div>
{/key}
layout.svelte:
<script>
import { googleMap } from "./../stores.js";
import { page } from "$app/stores";
import Footer from "$lib/Footer.svelte";
import Header from "$lib/Header.svelte";
import PageTransitions from "$lib/PageTransitions.svelte";
import Notifications from "svelte-notifications";
import "../app.css";
import { GOOGLE_API_KEY } from "$lib/constants.js";
import { setContext } from "svelte";
import { writable } from "svelte/store";
const url = `https://maps.googleapis.com/maps/api/js?key=${GOOGLE_API_KEY}&libraries=places,directions`;
let isSideMenuOpen = setContext("isSideMenuOpen", writable(false));
$: loaded = $googleMap.loaded;
const onLoad = () => {
$googleMap.loaded = true;
console.log("Google Maps SDK loaded…", window.google);
};
</script>
<svelte:head>
{#if !loaded}
<script
src={url}
type="application/javascript"
defer
async
on:load={onLoad}></script>
{/if}
</svelte:head>
<Notifications>
<div
class="h-screen flex flex-col bg-gray-50 dark:bg-gray-900"
class:overflow-hidden={isSideMenuOpen}
>
<Header />
<PageTransitions refresh={$page.path}>
<slot>Strona</slot>
</PageTransitions>
<Footer />
</div>
</Notifications>
modules.svelte(示例组件):
<script context="module">
export const ssr = false;
export const prerender = true;
export async function load({ session }) {
if (!session.authenticated) {
return {
status: 302,
redirect: "/auth/login",
};
}
return {
props: {
authenticated: session.authenticated,
token: session.token,
user: session.user,
},
};
}
</script>
<script>
import { slide } from 'svelte/transition';
import GenericTable from "$lib/GenericTable.svelte";
import { profile, modules } from "../../stores";
import { getModuleDataURL, getModuleTypeDataURL, get, del } from "$lib/api";
import GenericRow from "$lib/Rows/ModuleRow.svelte";
import { onMount } from "svelte";
import Loader from "$lib/Loader.svelte";
import { getNotificationsContext } from "svelte-notifications";
const { addNotification } = getNotificationsContext();
let loading = false;
export let authenticated;
export let token;
export let user;
const siteHeader = "Moduły";
const rootPath = "modules";
let filters = {
moduleType: null,
imei: "",
serialNumber: "",
phoneNumber: "",
isActive: null,
desc: "",
id: null,
};
const filtersChange = () => {
let filteredItems = [...$modules.items];
$modules.filters = false;
if (
filters.moduleType ||
filters.id ||
filters.desc.length > 2 ||
filters.isActive !== null ||
filters.imei.length > 2 ||
filters.serialNumber.length > 2 ||
filters.phoneNumber.length > 2
) {
$modules.filters = true;
}
if (filters.moduleType) {
filteredItems = filteredItems.filter(
(item) => item.moduleType.id === filters.moduleType.value
);
}
if (filters.isActive !== null) {
filteredItems = filteredItems.filter(
(item) => item.isActive === filters.isActive
);
}
if (filters.imei.length > 2) {
filteredItems = filteredItems.filter((item) =>
item.imei.includes(filters.imei)
);
}
if (filters.serialNumber.length > 2) {
filteredItems = filteredItems.filter((item) =>
item.serialNumber.includes(filters.serialNumber)
);
}
if (filters.phoneNumber.length > 2) {
filteredItems = filteredItems.filter((item) =>
item.phoneNumber.includes(filters.phoneNumber)
);
}
if (filters.desc.length > 2) {
filteredItems = filteredItems.filter((item) =>
item.desc ? item.desc.includes(filters.desc) : false
);
}
if (filters.id === "DESC") {
filteredItems.sort((a, b) => (a.id > b.id ? 1 : -1));
} else if (filters.id === "ASC") {
filteredItems.sort((a, b) => (a.id < b.id ? 1 : -1));
}
$modules.filteredModules = filteredItems;
};
const urlModuleType = getModuleTypeDataURL();
const urlModuleData = getModuleDataURL();
const deleteItem = async (id) => {
loading = true;
const url = getModuleDataURL() + "?id=" + id;
try {
const result = await del(url, token, false);
if (result.status) {
$modules.items = $modules.items.filter((item) => id !== item.id);
$modules.filteredModules = $modules.filteredModules.filter(
(item) => id !== item.id
);
addNotification({
text: "Usunięto moduł o id - " + id,
position: "bottom-right",
type: "success",
removeAfter: 4000,
});
} else throw result.message;
} catch (err) {
console.error(err);
addNotification({
text: err,
position: "bottom-right",
type: "danger",
removeAfter: 4000,
});
}
loading = false;
};
onMount(async () => {
loading = true;
$modules.filters = false;
$modules.filteredGroups = [];
try {
const resultModules = await get(
urlModuleData + "?$orderby=isActive desc,id",
token
);
if (resultModules.data) $modules.items = resultModules.data.items;
const resultModuleTypes = await get(urlModuleType, token);
if (resultModuleTypes.data)
$modules.moduleTypes = resultModuleTypes.data.items;
} catch (err) {
console.error(err);
addNotification({
text: "Podczas komunikacji z serwerem wystąpił błąd. Spróbuj raz jeszcze. Jeśli problem się powtórzy, skontaktuj się z administratorem aplikacji.",
position: "bottom-right",
type: "danger",
removeAfter: 4000,
});
}
loading = false;
});
let virtualListData = [];
$: virtualListData = $modules.filters
? $modules.filteredModules
: $modules.items;
let moduleTypes = [];
$: moduleTypes = $modules.moduleTypes.map((item) => ({
value: item.id,
label: item.type,
}));
const widths = ["5%", "15%", "10%", "10%", "15%", "15%", "20%", "10%"];
let listTableHead = [];
$: listTableHead = [
{
id: 1,
name: "ID",
filter: true,
type: "id",
},
{
id: 2,
name: "IMEI",
filter: true,
label: "imei",
type: "text",
placeholder: "Wpisz minimum 3 znaki",
onInput: filtersChange,
},
{
id: 3,
name: "Typ modułu",
filter: true,
label: "moduleTypes",
type: "select",
placeholder: "-",
items: moduleTypes,
select: (e) => {
filters.moduleType = e.detail;
filtersChange();
},
clear: () => {
filters.moduleType = null;
filtersChange();
},
},
{
id: 4,
name: "Aktywność",
filter: true,
label: "isActive",
type: "select",
placeholder: "-",
items: [
{ value: true, label: "Aktywny" },
{ value: false, label: "Nieaktywny" },
],
select: (e) => {
filters.isActive = e.detail.value;
filtersChange();
},
clear: () => {
filters.isActive = null;
filtersChange();
},
},
{
id: 5,
name: "Numer telefonu",
filter: true,
label: "phoneNumber",
type: "text",
placeholder: "Wpisz minimum 3 znaki",
onInput: filtersChange,
},
{
id: 6,
name: "Numer seryjny",
filter: true,
label: "serialNumber",
type: "text",
placeholder: "Wpisz minimum 3 znaki",
onInput: filtersChange,
},
{
id: 7,
name: "Opis",
filter: true,
label: "desc",
type: "text",
placeholder: "Wpisz minimum 3 znaki",
onInput: filtersChange,
},
{
id: 8,
name: "Dodaj nowy moduł",
type: "addNew",
},
];
</script>
<main class="w-full flex flex-1 overflow-y-auto bg-red-100">
<div class="p-2 overflow-y-auto flex flex-1">
{#if loading}
<Loader />
{/if}
<div
transition:slide
style="display:{loading ? 'none' : 'block'}"
class="flex flex-col flex-1 items-center justify-center"
>
<GenericTable
{siteHeader}
{filtersChange}
{deleteItem}
{virtualListData}
{widths}
{listTableHead}
{filters}
{rootPath}
{GenericRow}
/>
</div>
</div>
</main>
我尝试了什么:
- 我试图从 layout.svelte 组件中删除 PageTransitions,它有助于 onMount 功能,但页面看起来很糟糕
- 我尝试将过渡分别移动到每个组件,部分有效但并非所有地方都有效
- 试图将 onMount 函数内容移动到 Load 函数,但我无法编译
为什么会这样?
记录了一个相关问题 here(没有解决方案)。
根本原因是时间问题。
layout.svelte
更改 PageTransitions.svelte
中的 <slot/>
。
这会导致新的 slot/page 加载甚至触发 onMount。
新页面也会在屏幕上显示几毫秒,因为尚未发生转换!
插槽更改后,Svelte 会触发 $page
存储进行更新。
在您的代码中,这会导致转换更新 ({#key refresh}
),因此会再次加载 <slot/>
。
如果不修改 Svelte 本身,就无法更改 <slot/>
交换和 $page
存储集之间的时间。
此代码触发转换并且只导致一个组件挂载。
layout.svelte:
<script context="module">
export const load = async ({ url }) => ({ props: { refresh: url } });
</script>
<script>
import { googleMap } from "./../stores.js";
import Footer from "$lib/Footer.svelte";
import Header from "$lib/Header.svelte";
import PageTransitions from "$lib/PageTransitions.svelte";
import Notifications from "svelte-notifications";
import "../app.css";
import { GOOGLE_API_KEY } from "$lib/constants.js";
import { setContext } from "svelte";
import { writable } from "svelte/store";
export let refresh;
const url = `https://maps.googleapis.com/maps/api/js?key=${GOOGLE_API_KEY}&libraries=places,directions`;
let isSideMenuOpen = setContext("isSideMenuOpen", writable(false));
$: loaded = $googleMap.loaded;
const onLoad = () => {
$googleMap.loaded = true;
console.log("Google Maps SDK loaded…", window.google);
};
</script>
<svelte:head>
{#if !loaded}
<script
src={url}
type="application/javascript"
defer
async
on:load={onLoad}></script>
{/if}
</svelte:head>
<Notifications>
<div
class="h-screen flex flex-col bg-gray-50 dark:bg-gray-900"
class:overflow-hidden={isSideMenuOpen}
>
<Header />
<PageTransitions {refresh}>
<slot>Strona</slot>
</PageTransitions>
<Footer />
</div>
</Notifications>