1

我正在尝试从使用 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>

4

1 回答 1

2

在 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 与真实 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();
  }
}

我之前曾问过一个关于等待下一次渲染的问题,这会使这更干净一些,但我认为目前这不太容易。我将来可能会为此创建一个功能请求。

于 2020-11-16T14:35:39.783 回答