Миксины
Миксины классов — это шаблон совместного использования кода между классами с помощью стандартного JavaScript. В отличие от шаблонов композиции «имеет», таких как реактивные контроллеры, где класс может содержать контроллер для добавления поведения, миксины реализуют композицию «является», при которой миксин заставляет сам класс быть экземпляром добавляемого поведения.
Вы можете использовать миксины для настройки компонента 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/composition/mixins/