【问题标题】:How to forward an event through the third-party Svelte component?如何通过第三方 Svelte 组件转发事件?
【发布时间】:2021-06-07 13:47:11
【问题描述】:

我有一个嵌套组件的层次结构 (Svelte 3.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?我在生命周期代码 (https://github.com/sveltejs/svelte/blob/v3.35.0/src/runtime/internal/lifecycle.ts#L64) 中看到了 bubble 方法,但我不明白如何正确利用该逻辑。还是有更方便的方法?

更新:还有像$$props$$restProps这样的“神奇”道具,它们用于通过元素属性转发组件属性的值,但对于on:-like指令没有类似的“技巧” .

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

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

./Application.svelte

<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>

./Navigation/Menu.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>

./Page/AboutPage.svelte

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

Route 是来自外部库的“第三方”组件,它提供 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) -&gt; Route (library) -&gt; Application (project-level)。但是,很明显,我无法做到这一点(并且没有 "$$restDirectives"/"$$forwardMePlease" 技巧可以做到这一点)。

我认为这只是一种糟糕的做事方式,认为“事件”概念是一个全局性的东西(来自 java/php 样式的事件调度程序)。

【问题讨论】:

  • 很好奇,但是,当我开始我的项目时(3.24.0) - 这是可能的(不知道如何)。升级到3.35.0 后 - 我的带有事件调度逻辑的代码不再工作了:)
  • 如果 B 是第三方库,如何让 C 成为 B 的孩子?如果这是通过使用插槽,您可以直接在插槽 B 上添加事件。
  • 增加了一个例子。是的,我可以(至少 3.24.0 版本是这样),但 B 只是路由库和“页面组件”(C)之间的“桥梁”,它为主应用程序组件(A)发出事件,例如更改共享页面标题。
  • 您特别询问了如何使用 Svelte 的 on: 样式事件来做到这一点。如果这不是一成不变的,您可以使用自定义 dom 事件,例如我的示例:svelte.dev/repl/f1b23c6b95be4dfb9cad18dbba20d1f1?version=3.35.0
  • 哇!这完全符合我的要求,谢谢您的分享,先生!因此,实际的解决方案可能是 - 在 DOM 上使用某种“全局”事件或其他杠杆,如上下文/存储。

标签: svelte


【解决方案1】:

这似乎不是很简单,但我找到了两种方法可以解决这个问题,都涉及到 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} />

老实说,我什至不知道第二种方法是可能的,我会强烈反对它,认为它更像是一种学术思想......

【讨论】:

  • 无需分派和转发分派的事件。使用 setContext 将事件处理程序从 A 传递给 C。因此您可以从 C 调用 A 中的事件处理程序。
  • 感谢您分享这个!方式 1 看起来不错(包装器),但我在发布问题之前测试了类似的东西,遗憾的是,它不适用于我的情况(直到我将“脏”修复直接放入第三方组件的内部( on:customevent 转发指令)。[更新:可能我测试错了,应该深入研究它]。也许是因为 svelte:component 语句的动态特性。方式 2 让我心烦意乱,我不确定我可以长期维护这样的代码库。谢谢你,先生!
猜你喜欢
  • 2021-01-25
  • 2012-10-25
  • 2020-11-21
  • 1970-01-01
  • 2019-09-01
  • 1970-01-01
  • 2011-05-29
  • 2018-07-22
  • 2018-11-23
相关资源
最近更新 更多