如何防止在 StencilJs 中点击禁用按钮

How to prevent onClick on disabled button in StencilJs

我创建了自己的自定义按钮,如下所示


@Component({
    tag: "idv-button",
    styleUrl: "idv-button.scss",
    shadow: true,
})
export class IdvButton {
    @Prop({ reflect: true }) text: string;
    @Prop({ reflect: true }) disabled: boolean = false;

    render(): any {
        return (
            <button disabled={this.disabled} onClick={this.onClick}>
                <span class="btn-text">{this.text}</span>
            </button>
        );
    }

    private onClick(event: Event): void {
        if (this.disabled) {
            console.log("prevent default", event); // it doesn't come here at all
            event.preventDefault();
        }
    }

}

并在 stenciljs 的另一个组件中像这样使用它

                        <idv-button
                            text="my button"
                            disabled={true}
                            onClick={this.startClick.bind(this)}>
                        </idv-button>

问题是虽然按钮是 disabled 并且即使我试图阻止默认,但 onClick 仍然发生并且 this.startClick 被调用

我该如何解决这个问题?

我认为 on... 属性如何与自定义元素一起使用存在一些混淆。在 Stencil 组件上,如果属性以 on 开头,那么它将成为与属性其余部分同名的事件处理程序(大小写会自动转换)。例如如果您的组件发出一个名为 myEvent 的事件(使用 Stencil 的 @Event 装饰器和 EventEmitter 类型),那么您可以使用 onMyEvent 属性为该事件添加一个监听器到您的组件.文档 here.

中的示例对此进行了解释

在您的情况下,您要向组件添加一个 onClick 属性,该属性处理该元素的 click 事件,这是一个原生 DOM 事件,不不需要设置,我。 e.任何 HTML 或自定义元素在被单击时都会触发 click 事件(不仅仅是按钮)。

所以即使你的组件就像

@Component({ tag: "idv-button" })
export class IdvButton {
    render() {
        return <p>Hello, World!</p>;
    }
}

而且你用得像

<idv-button onClick={console.log} />

您每次单击文本时仍会收到 click 事件。


我认为您想要实现的是将 onClick 处理程序传递给底层按钮。最简单的方法是将处理函数作为 prop 传递。

import { Component, h, Host, Prop } from '@stencil/core';

@Component({ tag: "idv-button", shadow: true })
export class IdvButton {
    @Prop() disabled?: boolean;
    @Prop() clickHandler: (e: MouseEvent) => void;

    render() {
        return (
            <button disabled={this.disabled} onClick={this.clickHandler.bind(this)}>
                <slot />
            </button>
        );
    }
}
<idv-button clickHandler={console.log}>
    My Button
</idv-button>

不确定您是否真的需要绑定 this,并且还更改了它以将内容作为插槽传递,但请随时放弃该建议。

顺便说一句,调用道具 onClick 非常诱人,但这会与原生 click 事件的 on... 事件处理发生冲突,因此 Stencil 会在编译时警告您-时间。


另一种解决方案是在设置 disabled 属性时将 pointer-events: none 添加到主机。这就是 Ionic 的 ion-button 所做的(参见 button.tsx#L220 and button.scss#L70-L74)。

在你的情况下它会是这样的

import { Component, Host, h, Prop } from '@stencil/core';

@Component({ tag: "idv-button", shadow: true })
export class IdvButton {
    @Prop() disabled?: boolean;

    render() {
        return (
            <Host style={{ pointerEvents: this.disabled ? 'none' : undefined }}>
                <button disabled={this.disabled}>
                    <span class="btn-text">
                        <slot />
                    </span>
                </button>
            </Host>
        );
    }
}

onClick 属性会自动作用于您的组件,因为它是默认事件)

问题分析

禁用不是禁用

一个问题是,<idv-button disabled> 的计算结果为 this.disabled===""(空字符串),它被解释为 false 而不是 true

Chrome 捕获 onClick

另一个问题,在解决了上述问题后,它在 Firefox 中可以正常工作,但在 Chrome 中却不行。 Chrome 不给你机会自己处理 onClick,Chrome 处理它。

点击保留

保留的onClick的处理方式完全不同。所有 on… 属性都可以在 HTML 环境中采用字符串中的函数。但是如果你定义了自己的函数,没有以on…-前缀命名,你不能直接在HTML中添加函数。所以,你必须用不同的名字定义你自己的函数,因为 onClick 的特殊行为,所以你需要在普通的 HTML 中特殊处理它。在 JSX 中,一切都符合预期。

提示:名称onClick是保留的,所以是隐式的,不需要添加为@Prop

普通函数属性HTML

如果您需要在普通 HTML 环境(不是 JSX)中添加函数,则需要单独添加函数。另见:Pass functions to stencil component.

<idv-button>...</idv-button>
<script>
  document.currentScript.previousElementSibling.clickHandler =
      function() { ... }
</script>

(将函数clickHandler添加到脚本上方的元素)

简单的解决方案

顺便说一句:我更喜欢将文本放在标签内,所以我使用 <slot/>

组件定义

以下解决了所有这些问题并完美运行:

Component({tag: 'idv-button'})

export class IdvButton {

  @Prop() disabled?: any
  @Prop() clickHandler: (e: MouseEvent) => void;

  render() {
    return (
      <button
        disabled={this.disabled || this.disabled === ""}
        onClick={this.clickHandler}
      >
        <slot />
      </button>

    )
  }

}

在非 JSX Plain 中使用-HTML

要在普通 HTML 中使用它,您需要在单独的 JavaScript 中添加函数(这里我将相同的函数添加到所有 idv-button-元素):

<html>
  <head>
    <meta charset="utf-8" />
    <script type="module" src="/build/my-webcomponents.esm.js"></script>
    <script nomodule src="/build/my-webcomponents.js"></script>
  </head>
  <body>
    <idv-button>Enabled</idv-button>
    <idv-button disabled>Disabled</idv-button>
    <script>
      var allButtonInstances = document.querySelectorAll('idv-button')
      allButtonInstances.forEach(e => e.clickHandler = function() {
        alert('here');
      })
    </script>
  </body>
</html>

我来晚了,但是用 {...(this.disabled || null)} 代替 disabled={this.disabled} 怎么样?