Aurelia如何将所有属性转发到自定义组件

时间:2018-01-06 06:39:09

标签: aurelia aurelia-binding aurelia-framework

我在不同的项目中使用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. 我必须绑定(或调用)on-click属性,这使得它与本地属性的工作方式完全不同。为了解决这个问题,我 尝试使用DOM dispatchEvent API

  3. 这是我试过的:

    <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解决这些问题?任何想法都将不胜感激。

1 个答案:

答案 0 :(得分:2)

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

    3
   /  \
  3    3
 / \  / \
1   2 3  4

并像这样使用它。

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>

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