Svelte子组件更新后父组件重执行函数的无限循环问题解决
解决Svelte子组件更新后父组件无限循环请求API的问题
问题根源
- 父组件响应式触发逻辑冗余:父组件中
$:{ getData(tableFilterDetail) }会监听tableFilterDetail的引用变化,只要子组件重新赋值该对象(哪怕属性值没变化),就会触发getData。 - 子组件不必要的对象替换:子组件每次更新状态时都会创建新的
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
相关产品推荐
相关产品推荐

