OnMount вызывается дважды

У меня есть страница макета, и каждый компонент наследуется от нее. Моя проблема в том, что функция 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.info("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. Я попытался удалить PageTransitions из компонента layout.svelte, это помогает с точки зрения функции onMount, но страница выглядит плохо.
  2. Пробовал двигать переходы к каждому компоненту отдельно, частично работает но не везде
  3. Пытался переместить содержимое функции onMount в функцию Load, но не могу скомпилировать

Почему это происходит?

Для устранения неполадок, не могли бы вы добавить $: console.info(refresh); в свой PageTransitions.svelte? Я подозреваю, что переменная «обновить» изменяется, и поэтому ваша страница перезагружается из-за {#обновления ключа}

miwin 01.04.2022 11:59

Да, когда я перемещаюсь между страницами, значение меняется. Я получил: Refresh => / Refresh => / Groups Refresh => / Modules Refresh => / Objects Refresh => / Users Refresh => / Routes Итак, Refresh равен текущему пути к странице. Любая идея, как правильно реализовать корневой переход?

PAV 01.04.2022 16:42
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
2
84
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Существует связанная проблема, задокументированная здесь (без решения).

Основной причиной является проблема времени. layout.svelte заменяет <slot/> на PageTransitions.svelte. Это приводит к загрузке нового слота/страницы и даже запуску onMount.

Новая страница также будет видна на экране в течение нескольких миллисекунд, поскольку перехода еще не произошло!

После изменения слота Svelte запускает магазин $page для обновления. В вашем коде это приводит к переходу на обновление ({#key refresh}) и, следовательно, к повторной загрузке <slot/>.

Невозможно изменить время между обменом <slot/> и магазином $page без изменения самого Svelte.

Этот код запускает переход и вызывает монтирование только одного компонента.

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.info("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>

Другие вопросы по теме