问题描述
我正在尝试从StencilJS制成的组件中安全地删除DOM节点。
但是,根据调用此方法的时间,我遇到了问题。如果调用得太早,则它还没有DOM节点引用-它是undefined
。
下面的代码显示了组件代码(使用StencilJS)和HTML页面。
在页面脚本中调用alert.dismiss()
是有问题的。单击按钮调用相同的方法效果很好。
有一种安全的方法remove()
吗? StencilJS是否提供一些资源,我应该测试还是应该等待?
import {
Component,Element,h,Method
} from '@stencil/core';
@Component({
tag: 'my-alert',scoped: true
})
export class Alert {
// Reference to dismiss button
dismissButton: HTMLButtonElement;
/**
* StencilJS lifecycle methods
*/
componentDidLoad() {
// dismiss button click handler
this.dismissButton.addEventListener('click',() => this.dismiss());
}
// If this method is called from "click" event (handler above),everything is ok.
// If it is called from a script executed on the page,this.dismissButton may be undefined.
@Method()
async dismiss() {
// Remove button from DOM
// ** But this.dismissButton is undefined before `render` **
this.dismissButton.remove();
}
render() {
return (
<div>
<slot/>
<button ref={el => this.dismissButton = el as HTMLButtonElement} >
dismiss
</button>
</div>
);
}
}
<!DOCTYPE html>
<html lang="pt-br">
<head>
<title>App</title>
</head>
<body>
<my-alert>Can be dismissed.</my-alert>
<script type="module">
import { defineCustomElements } from './node_modules/my-alert/alert.js';
defineCustomElements();
(async () => {
await customElements.whenDefined('my-alert');
let alert = document.querySelector('my-alert');
// ** Throw an error,because `this.dismissButton`
// is undefined at this moment.
await alert.dismiss();
})();
</script>
</body>
</html>
解决方法
删除模板中的DOM节点有多种方法。
最简单的方法是像其他任何元素一样,仅在元素上调用remove()
:
document.querySelector('my-alert').remove();
另一种方法是拥有一个管理my-alert
消息的父容器。这对于诸如通知之类的东西尤其有用。
@Component({...})
class MyAlertManager {
@Prop({ mutable: true }) alerts = ['alert 1'];
removeAlert(alert: string) {
const index = this.alerts.indexOf(alert);
this.alerts = [
...this.alerts.slice(0,index),...this.alerts.slice(index + 1,0),];
}
render() {
return (
<Host>
{this.alerts.map(alert => <my-alert text={alert} />)}
</Host>
);
}
}
还有其他选项,具体取决于实际用例。
更新
在您的特定情况下,我将仅有条件地渲染关闭按钮:
export class Alert {
@State() shouldRenderDismissButton = true;
@Method()
async dismiss() {
this.shouldRenderDismissButton = false;
}
render() {
return (
<div>
<slot/>
{this.shouldRenderDismissButton && <button onClick={() => this.dismiss()}>
Dismiss
</button>
</div>
);
}
}
通常我不建议直接在Stencil组件中手动操作DOM,因为虚拟DOM与真实DOM不同步,这可能导致下一个渲染问题。
如果确实需要等待组件渲染,则可以使用Promise
:
class Alert {
loadPromiseResolve;
loadPromise = new Promise(resolve => this.loadPromiseResolve = resolve);
@Method()
async dismiss() {
// Wait for load
await this.loadPromise;
// Remove button from DOM
this.dismissButton.remove();
}
componentDidLoad() {
this.loadPromiseResolve();
}
}
我之前曾问过a question about waiting for the next render,这会使它更清晰一些,但我认为目前尚不容易。将来我可能会为此创建功能请求。