如何通过第三方Svelte组件转发事件



我有一个嵌套组件(Svelte3.35.0)的层次结构,看起来像这样:

Component A
Component B (third-party)
Component C

组件C想要使用createEventDispatcherdispatch事件,并且组件A正在侦听。文件上写着:

如果on:指令在没有值的情况下使用,则组件将转发事件,这意味着组件的使用者可以侦听该事件。

问题是..默认行为将事件转发限制为";儿童->父";并且我无法访问组件B的代码(它是一个单独的库)。根据规则,我需要;向前";该事件在定义的层次结构上,即从B到A。

所以,如果我想适当地";气泡;从组件C到处理程序的事件,它在组件A中定义,我需要构建以下逻辑:

Component A have an implementation for the "onMyEventHandler" function and
it doesn't know anything about Component C (it shouldn't, for decoupling purposes).
Component A ---------------------- on:my-event={onMyEventHandler}
Component B ------------------ on:my-event    <-- just to forward up to the parent's parent
Component C -------------- dispatch('my-event')

问题是,如何使用on:eventname指令将事件从组件C正确传递到组件A?我在生命周期代码中看到了bubble方法(https://github.com/sveltejs/svelte/blob/v3.35.0/src/runtime/internal/lifecycle.ts#L64),但我不明白如何正确利用这种逻辑。或者有更方便的方法吗?

更新:还有"魔术;像$$props$$restProps这样的道具,用于通过元素属性转发组件属性的值,但没有类似的";技巧";对于类似on:的指令。

更新2:因此,我应该提供一个如何实现的示例(此外,在这一点上,我正在考虑使用writable存储和上下文)。

我使用的是svelte-routing库,我的代码如下所示:

/Application.svelte<---成分A

<Menu items={menuItems} />
...
<Router>
{#each menuItems as menuItem}
{#if menuItem.component !== MenuDivider}
<Route    <--- Component B
path={menuItem.path}
component={menuItem.component}    <--- Component C
on:app.event.page.shown={onPageShown}
/>
{/if}
{/each}
</Router>

/导航/菜单.svelte

<Router>
<ul class="menu">
{#each items as item}
{#if item.component === MenuDivider}
<MenuDivider label={item.title} />
{:else}
{#if item.url?.length > 0}
<li class="menu-item">
<Link to={item.url} getProps={onLinkUpdate}>
{item.title}
</Link>
</li>
{/if}
{/if}
{/each}
</ul>
</Router>

/页码/AboutPage.svelte

<div> page contents... </div>

CCD_ 11是";第三方";来自外部库的提供API的组件,以定义不同的"客户端"之间的客户端路由;虚拟的";页面(即页面组件,见component={menuItem.component}行)。页面有一个onMount,带有一些业务逻辑,用于通知主要应用程序组件不同的状态更改(例如标题附加)。因此,它会发出事件,顶级应用程序组件会试图捕捉它们(它不知道我们有多少页面和它们的名称)。

来自svelte-routingRoute具有以下签名:

{#if $activeRoute !== null && $activeRoute.route === route}
{#if component !== null}
<svelte:component this="{component}" location={$location} {...routeParams} {...routeProps} />
{:else}
<slot params="{routeParams}" location={$location}></slot>
{/if}
{/if}

如果我向它放置转发on:onMyEventHandler指令,如下所示:

<svelte:component this="{component}" location={$location} {...routeParams} {...routeProps} on:onMyEventHandler />

它将使用一个路径传播一个事件:AboutPage (project-level) -> Route (library) -> Application (project-level)。但是,很明显,我无法做到这一点(而且没有"$$restDirectives"/"$$forwardMePlease"的技巧)。

我认为这只是一种糟糕的做事方式;事件";概念是全局性的(来自java/php风格的事件调度器)。

这似乎不是很琐碎,但我发现了两种解决方法,都涉及ContextApi。

(注意,在下面的代码中,很多都被去掉了,就像进口组件一样)

使用包装器组件

第一种也是最干净的方法是构造一个中间组件,该组件将捕获事件并将其分派给父组件。

<!-- Parent -->
<EventHandler on:click="{() => console.log('click')}">
<ThirdParty component={MyComponent} />
</EventHandler>
<!-- EventHandler -->
<script>
import { createEventDispatcher, onMount, setContext } from 'svelte'
setContext('context-dispatch', createEventDispatcher())
</script>
<slot />
<!-- MyComponent -->
<script>
import { getContext } from 'svelte' 
const dispatch = getContext('context-dispatch') 
const handleClick = ev => dispatch('click', ev)
</script>
<button on:click={handleClick}>Click here</button>

向自身添加回调

第二种方法有点,需要深入研究Svelte的一些内部工作,这些工作没有记录,因此未来可能会发生变化(尽管不太可能)。它以某种方式涉及从自身内部向父组件添加事件回调。

<script>
import { createEventDispatcher, setContext } from 'svelte'
import { get_current_component } from 'svelte/internal'
const dispatch = createEventDispatcher()
setContext('context-dispatch', dispatch)
get_current_component().$$.callbacks['click'] = [
() => console.log('callback')
] 
</script>
<ThirdParty component={MyComponent} />

不过,老实说,我甚至不知道第二种方法是可能的,我强烈建议不要这样做,认为这更像是一种学术思想。。。

最新更新