如何在 StencilJS 组件中安全地操作 DOM?
How can I safely manipulate DOM in a StencilJS component?
我正在尝试从使用 StencilJS 制作的组件中安全地删除 DOM 节点。
我已将删除代码放在 public 方法中 - 这正是我需要的。
但是,根据调用此方法的时刻,我遇到了问题。如果调用得太早,它还没有 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>
有多种方法可以删除 Stencil 中的 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 与真实 [=38] 不同步=].
如果你真的需要等待组件渲染你可以使用 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 这会使它更干净一些,但我认为目前这不太容易实现。将来我可能会为此创建一个功能请求。
我正在尝试从使用 StencilJS 制作的组件中安全地删除 DOM 节点。
我已将删除代码放在 public 方法中 - 这正是我需要的。
但是,根据调用此方法的时刻,我遇到了问题。如果调用得太早,它还没有 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>
有多种方法可以删除 Stencil 中的 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 与真实 [=38] 不同步=].
如果你真的需要等待组件渲染你可以使用 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 这会使它更干净一些,但我认为目前这不太容易实现。将来我可能会为此创建一个功能请求。