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>

我尝试了什么:

  1. 我试图从 layout.svelte 组件中删除 PageTransitions,它有助于 onMount 功能,但页面看起来很糟糕
  2. 我尝试将过渡分别移动到每个组件,部分有效但并非所有地方都有效
  3. 试图将 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>