lit-element:如何有效地从父项到子项自定义元素共享属性

时间:2019-03-14 21:22:57

标签: javascript typescript lit-element lit-html

问题:是否可以在不触发其render函数的情况下将属性更改传播到子元素?当前,当我更新父级switchViewModeHandler中的属性时,它会触发子级的重新渲染。

用例:将父级切换到“编辑”模式也应为其所有子级切换相同的状态。

疑问:我应该使用自定义事件吗?问题在于它将是一个由嵌套元素组成的复杂网络,事件一旦发生,它将很快变得难以调试(Polymer已经遇到了这个问题)。

设置:父元素:

class ParentElement extends LitElement {
  @property() viewMode;

  constructor() {
    this.viewMode = ViewMode.editable;
  }

  static get properties() {
    return {
      viewMode: { type: String, reflect: true },
    };
  }

  private switchViewModeHandler(event: MouseEvent): void {
    this.viewMode =
      (this.viewMode === ViewMode.editing) ? ViewMode.editable : ViewMode.editing; // update my own edit mode
    const options: HTMLElement[] = (Array.from(this.children) as HTMLElement[]);
    options.forEach((item: HTMLElement) => {
      item.viewMode = this.viewMode;
    });
  }

  render() {
    return html`
        <p>parent</p><label class="switch-wrapper">toggle edit mode
          <input type="checkbox"
            ?checked="${this.viewMode === ViewMode.editing}"
            @click="${
              (event: MouseEvent): void => this.switchViewModeHandler(event)
            }"
          />
        </label>
        <slot></slot><!-- child comes from here -->
    `;
  }
}

子元素:

class ChildElement extends LitElement {
  @property() viewMode;

  constructor() {
    super();
    this.viewMode = ViewMode.editable;
  }

  static get properties() {
    return {
      viewMode: { type: String, reflect: true },
    };
  }

  render() {
    console.log('child render() called');
    return html`
      <div class="viewer">child viewer</div>
      <div class="editor mode-${this.viewMode}">child editor</div>
    `;
  }
}

标记:

<parent-element>
  <child-element data-type="special"></child-element
></parent-element>

编辑模式来自导入的一个简单枚举(此处省略):

export enum ViewMode {
  readOnly = 'readOnly',
  editable = 'editable',
  editing = 'editing',
}

这是一个可与以下代码一起使用的Codesandbox:https://codesandbox.io/s/v1988qmn75

1 个答案:

答案 0 :(得分:3)

向轻量级DOM子代传播状态是一种相对很少需要的模式,因此我们尚未对其进行充分的记录,但是在某些情况下(如您的情况)有必要。

正确的方法实际上取决于容器元素的子元素如何受到控制。如果您(也是父元素的作者)也是唯一的用户,并且确切地知道了子元素的含义,那么您将拥有更大的灵活性。

我能想到的选项是:

  • 无论类型如何,始终在子级上设置属性。就像您在lit-html中使用<child prop=${x}></child>语法一样。
  • 向孩子们开火。这是很不相关的,但是如果您没有来自第三方的孩子,那么付出的代价却几乎没有好处。
  • 让孩子向父母注册。对孩子的责任更大,但要利用孩子的生命周期来订阅状态更改。

我想说,您在事件处理程序中设置子状态的方法并不遥远,除了它与用户交互事件而不是状态本身息息相关。

我会选择一些您总是在updated()中更新状态的东西:

class ParentElement extends LitElement {
  @property({ type: String, reflect: true })
  viewMode = ViewMode.editable;

  private _onSwitchClicked(event: MouseEvent): void {
    this.viewMode = (this.viewMode === ViewMode.editing)
      ? ViewMode.editable
      : ViewMode.editing;
  }

  private _onSlotChange() {
    this.requestUpdate();
  }

  updated() {
    for (const child of Array.from(this.children)) {
      child.viewMode = this.viewMode;
    }
  }

  render() {
    return html`
      <p>parent</p>
      <label class="switch-wrapper">toggle edit mode
        <input type="checkbox"
            ?checked=${this.viewMode === ViewMode.editing}
            @click=${this._onSwitchClicked}>
      </label>
      <slot @slotchange=${this._onSlotChange}></slot>
    `;
  }
}

如果子元素是LitElement且值是基元,则始终可以设置属性-在子元素中对它们进行脏检查是可以的。

请注意slotchange上的<slot>事件处理程序,以便我们观察子级。