如何防止 onClick 在 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,并将其更改为将内容作为插槽传递,但可以随意放弃该建议。

顺便说一句,调用 prop onClick 很诱人,但这会与本机 on... 事件的 click 事件处理发生冲突,因此 Stencil 会在编译时警告您.


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

在你的情况下,它会像

import { Component,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 捕捉点击

另一个问题,在解决上述问题后,它可以在 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 纯 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>