Spec-Zone.ru › Lit 2

Миксины

Миксины классов — это шаблон совместного использования кода между классами с помощью стандартного JavaScript. В отличие от композиционных шаблонов типа «имеет» (has-a), таких как реактивные контроллеры, где класс может содержать контроллер для добавления поведения, миксины реализуют композицию типа «является» (is-a), при которой миксин делает сам класс экземпляром добавляемого поведения.

Миксины можно использовать для настройки компонента Lit, добавляя API или переопределяя обратные вызовы жизненного цикла.

Основы миксинов

Миксины можно рассматривать как «фабрики подклассов», которые переопределяют класс, к которому применяются, и возвращают подкласс, расширенный поведением из миксина. Поскольку миксины реализуются с помощью стандартных выражений классов JavaScript, они могут использовать все идиомы, доступные при создании подклассов, например добавление новых полей и методов, переопределение существующих методов суперкласса и использование super.

Для удобства чтения в примерах на этой странице опущены некоторые типы TypeScript для функций миксинов. Подробнее о правильной типизации миксинов в TypeScript см. в разделе Миксины в TypeScript.

Чтобы определить миксин, напишите функцию, которая принимает superClass и возвращает новый класс, расширяющий его и добавляющий необходимые поля и методы:

const MyMixin = (superClass) => class extends superClass {
  /* class fields & methods to extend superClass with */
};

Чтобы применить миксин, просто передайте ему класс, чтобы создать подкласс с применённым миксином. Чаще всего пользователи применяют миксин непосредственно к базовому классу при определении нового класса:

class MyElement extends MyMixin(LitElement) {
  /* user code */
}

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

export const LitElementWithMixin = MyMixin(LitElement);
import {LitElementWithMixin} from './lit-element-with-mixin.js';

class MyElement extends LitElementWithMixin {
  /* user code */
}

Поскольку миксины классов — это стандартный шаблон JavaScript, а не специфичная для Lit функция, в сообществе доступно немало информации об использовании миксинов для повторного использования кода. Дополнительную информацию о миксинах можно найти в следующих материалах:

  • Миксины классов на MDN
  • Настоящие миксины с классами JavaScript, автор — Джастин Фагнани
  • Миксины в справочнике TypeScript.
  • Библиотека дедупликации миксинов от open-wc: в ней также обсуждается, когда использование миксинов может привести к дублированию, и как избежать его с помощью библиотеки дедупликации.
  • Соглашения для миксинов, принятые в библиотеке веб-компонентов Elix. Хотя материал не посвящён конкретно Lit, в нём содержатся полезные рекомендации по применению соглашений при определении миксинов для веб-компонентов.

Создание миксинов для LitElement

Миксины, применяемые к LitElement, могут реализовывать или переопределять любые стандартные обратные вызовы жизненного цикла пользовательского элемента, например constructor() или connectedCallback(), а также любые обратные вызовы жизненного цикла реактивного обновления, например render() или updated().

Например, следующий миксин будет записывать в журнал события создания, подключения и обновления элемента:

const LoggingMixin = (superClass) => class extends superClass {
  constructor() {
    super();
    console.log(`${this.localName} was created`);
  }
  connectedCallback() {
    super.connectedCallback();
    console.log(`${this.localName} was connected`);
  }
  updated(changedProperties) {
    super.updated?.(changedProperties);
    console.log(`${this.localName} was updated`);
  }
}

Обратите внимание: миксин всегда должен вызывать super для стандартных методов жизненного цикла пользовательского элемента, реализованных в LitElement. При переопределении обратного вызова жизненного цикла реактивного обновления рекомендуется вызывать метод super, если он уже существует в суперклассе (как показано выше при необязательном вызове с помощью цепочки операторов super.updated?.()).

Также обратите внимание, что миксины могут выполнять работу до или после базовой реализации стандартных обратных вызовов жизненного цикла — в зависимости от того, когда вызывается super.

Миксины также могут добавлять реактивные свойства, стили и API в расширенный элемент.

Миксин в примере ниже добавляет элементу реактивное свойство highlight и метод renderHighlight(), который пользователь может вызвать для обёртывания содержимого. Обёрнутое содержимое окрашивается в жёлтый цвет, если установлено свойство/атрибут highlight.

Обратите внимание, что в приведённом выше примере пользователь миксина должен вызвать метод renderHighlight() из своего метода render(), а также добавить static styles, определённые миксином, в стили подкласса. Условия такого взаимодействия между миксином и пользователем определяются самим миксином и должны быть задокументированы его автором.

Миксины в TypeScript

При написании миксинов LitElement в TypeScript следует учитывать несколько нюансов.

Типизация суперкласса

Если необходимо, следует ограничить аргумент superClass типом класса, который, как ожидается, будут расширять пользователи. Это можно сделать с помощью обобщённого вспомогательного типа Constructor, как показано ниже:

import {LitElement} from 'lit';

type Constructor<T = {}> = new (...args: any[]) => T;

export const MyMixin = <T extends Constructor<LitElement>>(superClass: T) => {
  class MyMixinClass extends superClass {
    /* ... */
  };
  return MyMixinClass as /* see "typing the subclass" below */;
}

Пример выше гарантирует, что класс, переданный миксину, наследуется от LitElement, чтобы миксин мог использовать обратные вызовы и другие API, предоставляемые Lit.

Типизация подкласса

Хотя TypeScript поддерживает базовый вывод возвращаемого типа подкласса, созданного с помощью шаблона миксинов, у него есть существенное ограничение: выведенный класс не должен содержать членов с модификаторами доступа private или protected.

Поскольку в самом LitElement есть закрытые и защищённые члены, по умолчанию TypeScript выдаст ошибку «Property '...' of exported class expression may not be private or protected.» при возврате класса, расширяющего LitElement.

Есть два способа обойти это ограничение; оба предполагают приведение возвращаемого типа функции-миксина, чтобы избежать указанной выше ошибки.

Если миксин не добавляет новый общедоступный/защищённый API

Если ваш миксин только переопределяет методы или свойства LitElement и не добавляет собственный API, можно просто привести созданный класс к типу суперкласса T, переданного миксину:

export const MyMixin = <T extends Constructor<LitElement>>(superClass: T) => {
  class MyMixinClass extends superClass {
    connectedCallback() {
      super.connectedCallback();
      this.doSomethingPrivate();
    }
    private doSomethingPrivate() {
      /* does not need to be part of the interface */
    }
  };
  // Cast return type to the superClass type passed in
  return MyMixinClass as T;
}

Если миксин добавляет новый общедоступный/защищённый API

Если миксин добавляет защищённый или общедоступный API, который пользователи должны иметь возможность использовать в своём классе, необходимо отдельно определить интерфейс миксина и его реализацию, а затем привести возвращаемый тип к пересечению интерфейса миксина и типа суперкласса:

// Define the interface for the mixin
export declare class MyMixinInterface {
  highlight: boolean;
  protected renderHighlight(): unknown;
}

export const MyMixin = <T extends Constructor<LitElement>>(superClass: T) => {
  class MyMixinClass extends superClass {
    @property() highlight = false;
    protected renderHighlight() {
      /* ... */
    }
  };
  // Cast return type to your mixin's interface intersected with the superClass type
  return MyMixinClass as Constructor<MyMixinInterface> & T;
}

Применение декораторов в миксинах

Из-за ограничений системы типов TypeScript декораторы (например, @property()) необходимо применять к объявлению класса, а не к выражению класса.

На практике это означает, что в миксинах TypeScript необходимо объявить класс, а затем вернуть его, вместо того чтобы возвращать выражение класса непосредственно из функции со стрелкой.

Поддерживается:

export const MyMixin = <T extends LitElementConstructor>(superClass: T) => {
  // ✅ Defining a class in a function body, and then returning it
  class MyMixinClass extends superClass {
    @property()
    mode = 'on';
    /* ... */
  };
  return MyMixinClass;
}

Не поддерживается:

export const MyMixin = <T extends LitElementConstructor>(superClass: T) =>
  // ❌ Returning class expression directly using arrow-function shorthand
  class extends superClass {
    @property()
    mode = 'on';
    /* ... */
  }

Редактировать эту страницу

© Google LLC
Licensed under the Creative Commons Attribution 3.0 Unported License.
https://lit.dev/docs/v2/composition/mixins/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API