Svelte中onMount晚于父组件调用子组件方法执行的原因?
问题分析与解决
最小示例代码
Component A
<script lang="ts"> import {tick} from 'svelte'; import LocationSelector from './LocationSelector.svelte'; let locationSelector: LocationSelector; let selectedCompany: string; async function updateAvailableLocations(){ await tick(); locationSelector.setAvailableLocations(selectedCompany); } $: selectedCompany, updateAvailableLocations(); </script> <input type="text" bind:value={selectedCompany} /> {#if selectedCompany} <LocationSelector bind:this={locationSelector}/> {/if}
LocationSelector.svelte
<script lang="ts"> import { onMount } from 'svelte'; let availableLocations: Locations[]; export function setAvailableLocations(type: string) { for (const location of availableLocations ) { if(type == location.company) { // 下拉框设置可用地点的代码 } } } onMount(async () => { try { availableLocations = await api.getAllLocations(); } catch (error) { console.log("获取地点失败") } }) </script> <!-- 组件模板 -->
问题原因
当selectedCompany变为有效值时,{#if}条件满足,Svelte会立即创建LocationSelector组件实例,此时bind:this已经能拿到组件引用,但组件还没挂载到DOM上。
你用的await tick()仅会等待当前批次的DOM更新完成——这里的“更新完成”只是指组件实例被创建、开始渲染,而onMount钩子的触发时机是组件完全挂载到DOM之后,这个时机晚于tick()等待的DOM更新批次。具体执行顺序如下:
selectedCompany变化,触发响应式函数updateAvailableLocations- 执行
await tick(),等待当前DOM更新(此时组件实例创建,locationSelector完成赋值) - 调用
locationSelector.setAvailableLocations(selectedCompany),但此时onMount还未执行,availableLocations仍是空数组 - 组件挂载到DOM,
onMount触发,开始请求数据并赋值给availableLocations
这就导致setAvailableLocations执行时,依赖的availableLocations还未初始化,无法正常工作。
解决方案
方案1:用Props传递筛选条件,组件内部响应式处理
这是最符合Svelte设计理念的方式,避免直接调用组件方法:
修改LocationSelector.svelte:
<script lang="ts"> import { onMount } from 'svelte'; let availableLocations: Locations[]; export let selectedCompany: string; // 通过props接收筛选条件 // 当availableLocations和selectedCompany都就绪时自动筛选 $: if (availableLocations && selectedCompany) { const filteredLocations = availableLocations.filter(loc => loc.company === selectedCompany); // 将filteredLocations赋值给下拉框的选项变量 } onMount(async () => { try { availableLocations = await api.getAllLocations(); } catch (error) { console.log("获取地点失败") } }) </script>
修改Component A的组件调用:
{#if selectedCompany} <LocationSelector {selectedCompany}/> {/if}
方案2:组件内部暴露数据就绪的Promise
让组件提供一个Promise,外部等待数据加载完成后再调用方法:
修改LocationSelector.svelte:
<script lang="ts"> import { onMount } from 'svelte'; let availableLocations: Locations[]; export let locationsReady: Promise<void>; export function setAvailableLocations(type: string) { for (const location of availableLocations ) { if(type == location.company) { // 下拉框设置可用地点的代码 } } } locationsReady = new Promise(async (resolve) => { try { availableLocations = await api.getAllLocations(); resolve(); } catch (error) { console.log("获取地点失败"); resolve(); // 即使失败也resolve,避免外部一直等待 } }) </script>
修改Component A的updateAvailableLocations:
async function updateAvailableLocations(){ await tick(); await locationSelector.locationsReady; // 等待数据加载完成 locationSelector.setAvailableLocations(selectedCompany); }
方案3:组件挂载完成后通知外部
利用事件通知外部组件已挂载且数据就绪:
修改LocationSelector.svelte:
<script lang="ts"> import { onMount, createEventDispatcher } from 'svelte'; const dispatch = createEventDispatcher(); let availableLocations: Locations[]; export function setAvailableLocations(type: string) { for (const location of availableLocations ) { if(type == location.company) { // 下拉框设置可用地点的代码 } } } onMount(async () => { try { availableLocations = await api.getAllLocations(); dispatch('ready'); // 通知外部数据就绪 } catch (error) { console.log("获取地点失败") dispatch('ready'); } }) </script>
修改Component A:
<script lang="ts"> import LocationSelector from './LocationSelector.svelte'; let locationSelector: LocationSelector; let selectedCompany: string; let isLocationReady = false; // 当筛选条件和数据都就绪时执行方法 $: if (selectedCompany && isLocationReady) { locationSelector.setAvailableLocations(selectedCompany); } </script> <input type="text" bind:value={selectedCompany} /> {#if selectedCompany} <LocationSelector bind:this={locationSelector} on:ready={() => isLocationReady = true}/> {/if}
内容的提问来源于stack exchange,提问作者Nik
相关产品推荐
相关产品推荐

