问题描述
我创建了自己的自定义按钮,如下所示
@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#L220 和 button.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>