使用svelte-routing时,Meteor数据订阅在Svelte onMount中报错
我来帮你拆解这个问题并给出可行的解决方案:
问题根源分析
你遇到的核心问题是在onMount中调用Meteor.subscribe时,svelte-meteor-data包内部获取到的current_component并非当前组件,而是svelte-routing的Link或Route组件,导致on_destroy为null触发报错。
这背后的原因和Svelte的组件渲染生命周期、current_component的工作机制有关:
current_component是Svelte内部用来追踪当前正在执行渲染/生命周期的组件的变量- 当通过
Link导航时,onMount的执行时机和路由组件的切换逻辑存在竞态:Link组件的一些内部逻辑可能还在执行,导致current_component没有及时切换到目标组件 - 直接打印
current_component时能得到正确结果,是因为这行代码在组件初始化的同步阶段执行,此时current_component确实指向当前组件;而onMount是异步触发的,此时上下文已经被路由组件的操作干扰了
可行解决方案
方案1:使用svelte-meteor-data提供的subscribe工具(推荐)
不要直接调用Meteor.subscribe,而是用rdb:svelte-meteor-data包自带的subscribe方法,它会自动绑定当前组件的销毁回调,避免current_component的上下文问题:
<script> import { Meteor } from 'meteor/meteor'; import { onMount } from 'svelte'; import { useTracker, subscribe } from 'meteor/rdb:svelte-meteor-data'; import Artwork from '../../models/Artwork'; export let _id; onMount(() => { // 使用包内的subscribe方法,自动关联当前组件 const sub = subscribe('artwork', _id); // 可以通过sub.ready()判断订阅状态 }); $: artwork = useTracker(() => Artwork.findOne({_id})); </script>
方案2:在useTracker内部处理订阅
既然useTracker已经能正确关联当前组件,把订阅逻辑放到useTracker里也是更符合Svelte响应式的做法,还能自动和数据查询联动:
<script> import { useTracker } from 'meteor/rdb:svelte-meteor-data'; import Artwork from '../../models/Artwork'; export let _id; $: ({ artwork, isReady } = useTracker(() => { const sub = Meteor.subscribe('artwork', _id); return { artwork: Artwork.findOne({_id}), isReady: sub.ready() }; })); </script>
这种方式下,useTracker会自动管理订阅的生命周期,当组件销毁或_id变化时,会自动取消旧订阅并创建新订阅,完全不需要手动处理onMount和销毁逻辑。
方案3:手动绑定组件上下文(不推荐,仅作兜底)
如果你一定要在onMount中使用Meteor.subscribe,可以手动保存当前组件的引用,避免上下文丢失:
<script> import { current_component } from 'svelte/internal'; import { Meteor } from 'meteor/meteor'; import { onMount } from 'svelte'; import { useTracker } from 'meteor/rdb:svelte-meteor-data'; import Artwork from '../../models/Artwork'; export let _id; // 同步阶段保存当前组件引用 const thisComponent = current_component; onMount(() => { // 手动将订阅的销毁回调绑定到当前组件的on_destroy数组 const sub = Meteor.subscribe('artwork', _id); thisComponent.$$.on_destroy.push(() => sub.stop()); }); $: artwork = useTracker(() => Artwork.findOne({_id})); </script>
不过这种方式依赖Svelte内部的私有API($$.on_destroy),未来可能会有兼容性问题,所以优先推荐前两种方案。
为什么setTimeout能临时解决问题?
用setTimeout包裹订阅时,相当于把订阅逻辑推迟到了下一个事件循环,此时路由组件的切换逻辑已经执行完毕,current_component已经正确指向目标组件,所以不会报错。但这只是规避问题的临时手段,不是根本解决方案。
内容的提问来源于stack exchange,提问作者Jack Templeman

