Aurelia 如何将所有属性转发给自定义组件中的子元素

Aurelia how to forward all attributes to child element in custom component

我在不同的项目中同时使用 React 和 Aurelia。

我的 Aurelia 应用程序变得非常复杂,我真的很想处理重复的逻辑。假设我想要一个节流按钮,等待异步请求再次可点击,使用 React 非常简单:

我希望我的 <ThrottledButton /> 具有与本机 <button /> 完全相同的行为,除了它会自动阻止多个请求的发生,直到上一个请求完成或失败:

class MyComponent extends Component {
  handleClick = async e => {
    const response = await this.longRequest(e)
    console.log(`Received: `, response)
  }

  longRequest = async e => {
    return new Promise((res, rej) => {
      setTimeout(() => { res(e) }, 1000)
    })
  }

  render() {
    return <div>
      <button className="btn" onClick={this.handleClick}>Click me</button>
      <ThrottledButton className="btn" onClick={this.handleClick}>Click me</ThrottledButton>
    </div>
  }
}

下面是 <ThrottledButton />:

的实现
class ThrottledButton extends Component {
  constructor(props) {
    super(props)
    this.state = { operating: false }
  }

  render() {
    const { onClick, ...restProps } = this.props

    const decoratedOnClick = async e => {
      if (this.state.operating) return

      this.setState({ operating: true })

      try {
        await Promise.resolve(onClick(e))
        this.setState({ operating: false })
      } catch (err) {
        this.setState({ operating: false })
        throw err
      }
    }

    return <button onClick={decoratedOnClick} {...restProps}>{this.props.children}</button>
  }
}

现在我真的很想在 Aurelia 中实现相同(或相似)的东西,这里是用例组件:

<template>
  <require from="components/throttled-button"></require>

  <button
    class="btn"
    click.delegate="handleClick($event)">
    Click me
  </button>

  <throttled-button
    class="btn"
    on-click.bind="handleClick">
    Click me
  </throttled-button>
</template>

export class MyComponent {
  handleClick = async e => {
    const response = await this.longRequest(e)
    console.log(`Received: `, response)
  }

  async longRequest(e) {
    return new Promise((res, rej) => {
      setTimeout(() => { res(e) }, 1000)
    })
  }
}

这是我目前拥有的节流按钮:

<template>
  <button click.delegate="decoratedClick($event)" class.bind="class">
    <slot></slot>
  </button>
</template>

export class ThrottledButton {
  @bindable onClick = () => {}
  @bindable class

  constructor() {
    this.operating = false
  }

  decoratedClick = async e => {
    console.log(this.operating)

    if (this.operating) return
    this.operating = true

    try {
      await Promise.resolve(this.onClick(e))
      this.operating = false
    } catch (err) {
      this.operating = false
      throw err
    }
  }
}

如您所见,<throttled-button /> 与原生 <button /> 有很大不同,尤其是:

  1. 而不是类似于 React 的 <button {...props} /> 传播 运算符,我必须手动传递我绑定的每个属性 到 <throttled-button /> 到原生按钮。对于大多数组件,它是 巨大的努力,并可能导致许多错误。

  2. 我必须绑定(或调用)点击属性,这使得它的工作方式与本机的非常不同。为了解决这个问题,我 尝试使用 DOM dispatchEvent API

这是我尝试过的:

<template>
  <button ref="btn">
    <slot></slot>
  </button>
</template>

@inject(Element)
export class ThrottledButton {
  constructor(el) {
    this.el = el
    this.operating = false
  }

  attached = () => {
    this.btn.addEventListener('click', this.forwardEvent)
  }

  detached = () => {
    this.btn.removeEventListener('click', this.forwardEvent)
  }

  forwardEvent = e => {
    this.operating = true
    this.el.dispatchEvent(e) // no way to get the promise
  }
}

但是,无法获取对 promise 的引用,这使得节流变得不可能。

有希望在Aurelia解决这些问题吗?任何想法将不胜感激。

您可以使用自定义属性来解决问题。这是打字稿中的代码。

import { autoinject, customAttribute } from 'aurelia-framework';

@customAttribute('single-click')
@autoinject()
export class SingleClickCustomAttribute {
  clicked: (e: Event) => void;
  private value: () => Promise<any>;
  private executing: boolean = false;

  constructor(
    private element: Element
  ) {
    this.clicked = e => {
      if (this.executing) {
        return;
      }

      this.executing = true;
      setTimeout(async () => {
        try {
          await this.value();
        }
        catch (ex) {
        }
        this.executing = false;
      }, 100);
    };
  }

  attached() {
    this.element.addEventListener('click', this.clicked);
  }

  detached() {
    this.element.removeEventListener('click', this.clicked);
  }
}

然后像这样使用它。

<button single-click.call="someAsyncMethod()" type="button">...</button>

要调用的异步方法应该是这样的:

async someAsyncMethod(): Promise<void> {
    await this.saveData();
}

诀窍是通过 .call 传递异步函数并使用状态 属性。