lit-element: как эффективно использовать свойство родительского элемента для дочернего элемента - PullRequest
3 голосов
/ 15 марта 2019

Вопрос: Есть ли способ распространить изменение свойства на дочерний элемент, не вызывая его функцию 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>
    `;
  }
}

Markup:

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

Режим редактирования происходит из простого импортированного перечисления (здесь опущено):

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

Вот код-песочница для игры: https://codesandbox.io/s/v1988qmn75

1 Ответ

3 голосов
/ 18 марта 2019

Распространение состояния на свет DOM-дочерние элементы - это сравнительно редко необходимый шаблон, поэтому мы пока недостаточно хорошо его документировали, но в некоторых случаях, например, в вашем, это необходимо.

Правильный подход действительно зависит отнасколько контролируются дочерние элементы элемента контейнера.Если вы, автор родительского элемента, также являетесь его единственным пользователем и точно знаете, какими могут быть дети, тогда у вас есть больше гибкости.

Возможные варианты:

  • Всегда устанавливайте свойство для дочернего элемента независимо от его типа.Это похоже на синтаксис <child prop=${x}></child>, доступный в lit-html.
  • Запустить событие для дочерних элементов.Это очень разделено, но дороже за небольшую выгоду, если у вас нет детей от третьих лиц.
  • Попросите детей зарегистрироваться у родителей.Больше ответственности за ребенка, но он использует жизненные циклы ребенка, чтобы подписаться на изменения состояния.

Я бы сказал, что ваш подход к установке дочернего состояния в обработчике событий не за горами, за исключением того, чтооно слишком привязано к событию взаимодействия с пользователем, а не к самому состоянию.

Я бы пошел на что-то, где вы всегда обновляете состояние в 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>
    `;
  }
}

Если дочерние элементы - LitElementsи значения являются примитивами, можно всегда устанавливать свойства - они будут грязно проверены в дочерних элементах.

Обратите внимание на обработчик событий slotchange в <slot>, чтобы мы могли наблюдать дочерние элементы.

...