如何通过第三方 Svelte 组件转发事件? [英] How to forward an event through the third-party Svelte component?

查看:43
本文介绍了如何通过第三方 Svelte 组件转发事件?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个嵌套组件的层次结构(Svelte 3.35.0),如下所示:

组件A组件 B(第三方)组件 C

组件 C 想要使用 createEventDispatcher dispatch 一个事件,而组件 A 正在监听.文档说:

<块引用>

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

问题是..默认行为将事件转发限制为Child ->"家长"而且我无权访问组件 B 的代码(它是一个单独的库).根据规则,我需要转发"该事件在定义的层次结构上,即从 B 到 A.

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

组件 A 有一个onMyEventHandler"的实现.功能和它对组件 C 一无所知(出于解耦目的,它不应该).组件 A ---------------------- on:my-event={onMyEventHandler}组件 B ------------------ on:my-event <-- 只是转发给父级的父级组件 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:-like 指令.

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

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

./Application.svelte <--- 组件A

...<路由器>{#each menuItems as menuItem}{#if menuItem.component !== MenuDivider}<路线<---组件B路径={menuItem.path}组件={menuItem.component} <--- 组件Con:app.event.page.shown={onPageShown}/>{/如果}{/每个}</路由器>

./Navigation/Menu.svelte

<ul class="menu">{#每个项目作为项目}{#if item.component === MenuDivider}<MenuDivider label={item.title}/>{:别的}{#if item.url?.length >0}<li class="menu-item"><Link to={item.url} getProps={onLinkUpdate}>{item.title}</链接>{/如果}{/如果}{/每个}</路由器>

./Page/AboutPage.svelte

页面内容... </div>

Route 是第三方"来自外部库的组件,它提供 API 来定义不同虚拟"之间的客户端路由.页面(即页面组件,请参见 component={menuItem.component} 行).页面有一个带有一些业务逻辑的 onMount 来通知主要应用程序组件不同的状态变化(例如标题附加).所以它发出事件,顶级应用程序组件将尝试捕获它们(并且它不知道我们有多少页面及其名称).

svelte-routing 中的

Route 具有以下签名:

{#if $activeRoute !== null &&$activeRoute.route === 路线}{#if 组件 !== null}<svelte:component this="{component}";location={$location} {...routeParams} {...routeProps}/>{:别的}<slot params="{routeParams}";location={$location}>{/如果}{/如果}

如果我像这样放置一个转发 on:onMyEventHandler 指令给它:

它将使用路径传播事件:AboutPage(项目级)->路线(图书馆)->应用程序(项目级).但是,显然,我无法做到这一点(并且没有$$restDirectives"/$$forwardMePlease"技巧可以做到这一点).

我认为这只是一种糟糕的做事方式,认为一个事件"很糟糕.概念是一个全局的东西(来自 java/php 风格的事件调度器).

解决方案

这看起来不是很简单,但我找到了两种方法可以解决这个问题,都涉及 ContextApi.

(注意下面的代码中去掉了很多,比如导入组件)

使用包装组件

第一个也是最干净的方法是构建一个中间组件,该组件将捕获事件并将它们分派给父组件.

<!-- EventHandler --><脚本>import { createEventDispatcher, onMount, setContext } from 'svelte'setContext('context-dispatch', createEventDispatcher())<插槽/>

<脚本>导入 { getContext } 从 'svelte'const dispatch = getContext('context-dispatch')const handleClick = ev =>调度('点击',ev)<button on:click={handleClick}>点击这里</button>

向自身添加回调

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

<第三方组件={MyComponent}/>

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

I have a hierarchy of the nested components (Svelte 3.35.0), which looks like this:

Component A
    Component B (third-party)
        Component C

Component C wants to dispatch an event using createEventDispatcher and Component A is listening. The documentation says:

If the on: directive is used without a value, the component will forward the event, meaning that a consumer of the component can listen for it.

The problem is.. default behavior restricts event forwarding to "Child -> Parent" and I don't have access to the Component B's code (it is a separate library). According to the rules, I need to "forward" that event up on the defined hierarchy, i.e. from B to A.

So, if I want to properly "bubble" an event from Component C to the handler, which is defined in the Component A, I need to build the following logic:

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

The question is - how to properly pass an event from the Component C to the Component A using on:eventname directive? I've seen a bubble method in the lifecycle code (https://github.com/sveltejs/svelte/blob/v3.35.0/src/runtime/internal/lifecycle.ts#L64), but I don't understand how to properly utilize that logic. Or is there a more convenient approach?

Update: there are also "magic" props like $$props and $$restProps, which are used to forward values for the component properties via element attributes, but there are no similar "tricks" for on:-like directives.

Update 2: so, I should provide an example how it is possible (also, at this point i'm considering to use a writable store and contexts instead).

I'm using a svelte-routing library and my code looks like this:

./Application.svelte <--- Component 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>

./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 is a "third-party" component from the external library that provides API to define client-side routing between different "virtual" pages (i.e. page components, see the line component={menuItem.component}). The Page have an onMount with some business logic to inform the main application components about different state changes (e.g. title appends). So it emits events and the top-level application component will try to catch them (and it doesn't know how much pages we have and their names).

Route from the svelte-routing has the following signature:

{#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}

and if I place a forwarding on:onMyEventHandler directive to it like that:

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

It will propagate an event using a path: AboutPage (project-level) -> Route (library) -> Application (project-level). But, obviously, I'm not able to do that (and there are no "$$restDirectives"/"$$forwardMePlease" trick to do this).

I think it is just a bad way to do things, thought that an "event" concept is a global thing (coming from java/php-style event dispatchers).

解决方案

This does not seem very trivial, but I found two ways you could solve this, both involve the ContextApi.

(Note that in the code below a lot is stripped out, like importing components)

using a wrapper component

The first and cleanest approach is to construct an intermediate component that will capture the event and dispatch them to the parent.

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

Adding callbacks to self

The second approach is a bit dirty and requires digging into some internal workings of Svelte, these are undocumented so can change in the future (albeit unlikely they will). It somehow involves adding an event callback to the parent component from inside itself.

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

I will be honest though, I didn't even know the second approach was possible and I would highly recommend against it, consider it more of an academic thought...

这篇关于如何通过第三方 Svelte 组件转发事件?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆