You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Svelte子组件更新后父组件重执行函数的无限循环问题解决

解决Svelte子组件更新后父组件无限循环请求API的问题

问题根源

  1. 父组件响应式触发逻辑冗余:父组件中$:{ getData(tableFilterDetail) }会监听tableFilterDetail的引用变化,只要子组件重新赋值该对象(哪怕属性值没变化),就会触发getData。
  2. 子组件不必要的对象替换:子组件每次更新状态时都会创建新的tableFilterDetail对象,导致父组件误判参数变化发起API请求;请求完成后更新data和dataSize,又触发子组件状态更新,再次创建新对象,形成无限循环。

解决方案

1. 父组件:优化请求触发逻辑

  • 移除重复的onMount调用(响应式语句会在组件首次加载时自动执行)。
  • 给getData添加参数校验,避免重复请求相同参数。
  • 保留响应式语句监听tableFilterDetail,但通过参数校验过滤无效触发。

2. 子组件:避免无意义的对象替换

  • 不直接替换整个tableFilterDetail对象,仅更新实际变化的属性,保持对象引用不变,避免父组件无意义的响应式触发。

修改后的代码

父组件

<script lang="ts">
  import { Button } from "$lib/components/ui/button";
  import { SquarePlus } from "lucide-svelte";
  import Datatable from "$lib/components/datatable/datatable.svelte";
  import { getUserListAPI } from "@/apis/hook";
  import type { User, UserFilter } from "@/interfaces/user";

  let data: User[] = [];
  let dataSize = 0;
  let lastReq: UserFilter | null = null;
  
  let tableFilterDetail: UserFilter = {
    pageIndex: 1,
    pageLimit: 10
  };

  const getData = async (req: UserFilter) => {
    // 仅当参数真正变化时发起请求
    if (lastReq && lastReq.pageIndex === req.pageIndex && lastReq.pageLimit === req.pageLimit) {
      return;
    }
    lastReq = { ...req };
    
    const res = await getUserListAPI(req);
    data = res.data.rows;
    dataSize = res.data.count;
  };

  // 首次加载及参数变化时触发请求
  $: getData(tableFilterDetail);
</script>

<div>
  <div class="mb-2 w-full flex justify-between items-center">
    <h2 class="text-2xl font-bold tracking-tight">User</h2>
    <div class="flex items-center">
      <Button
        variant="success"
        class="ml-1 h-10 w-28 flex items-center justify-center rounded-lg "
      >
        <SquarePlus size="16" />New
      </Button>
    </div>
  </div>
  <Datatable {data} {columns} {dataSize} bind:tableFilterDetail/>
</div>

子组件

<script lang="ts">
......

  // 更新状态并同步筛选参数,仅修改属性而非替换对象
  $: {
    datatableAttr.update((state) => ({
      ...state,
      hiddenColumn: hideForId,
      dataSize: dataSize,
      pageSize: pageSize,
    }));
    
    // 仅当值真正变化时更新属性,保持对象引用不变
    const newPageIndex = $datatableAttr.currentPage;
    const newPageLimit = $datatableAttr.pageSize.value;
    if (tableFilterDetail.pageIndex !== newPageIndex) {
      tableFilterDetail.pageIndex = newPageIndex;
    }
    if (tableFilterDetail.pageLimit !== newPageLimit) {
      tableFilterDetail.pageLimit = newPageLimit;
    }
    
    tableData.set(data);
  }

</script>
......
<!-- 模板部分保持不变 -->
    <div class="flex items-center space-x-2">
      <label for="itemsPerPage" class="text-gray-700">Page shows:</label>
      <Select.Root portal={null} bind:selected={pageSize}>
        <Select.Trigger class="w-[130px]">
          <Select.Value />
        </Select.Trigger>
        <Select.Content>
          <Select.Group>
            {#each tableSize as size}
              <Select.Item value={size.value} label={size.label}
                >{size.label}</Select.Item
              >
            {/each}
          </Select.Group>
        </Select.Content>
        <Select.Input name="" />
      </Select.Root>
    </div>
    <div class="flex items-center space-x-2">
      <Pagination.Root
        count={$datatableAttr.dataSize}
        perPage={$datatableAttr.pageSize.value}
        let:pages
        let:currentPage
        bind:page={$datatableAttr.currentPage}
      >
        <Pagination.Content>
          <Pagination.Item>
            <Pagination.PrevButton />
          </Pagination.Item>
          {#each pages as page (page.key)}
            {#if page.type === "ellipsis"}
              <Pagination.Item>
                <Pagination.Ellipsis />
              </Pagination.Item>
            {:else}
              <Pagination.Item>
                <Pagination.Link {page} isActive={currentPage == page.value}>
                  {page.value}
                </Pagination.Link>
              </Pagination.Item>
            {/if}
          {/each}
          <Pagination.Item>
            <Pagination.NextButton />
          </Pagination.Item>
        </Pagination.Content>
      </Pagination.Root>
    </div>
......

效果说明

  • 组件首次加载时自动发起API请求,加载初始数据。
  • 子组件分页、每页条数变化时,仅更新tableFilterDetail的对应属性,父组件检测到属性值变化后发起新的API请求。
  • 通过参数校验避免重复请求相同参数,彻底解决无限循环问题。

内容的提问来源于stack exchange,提问作者cnfj

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 18:58:10