Spec-Zone.ru › Lit 3

Реактивные свойства

Компоненты Lit получают входные данные и хранят состояние в виде полей или свойств классов JavaScript. Реактивные свойства — это свойства, изменение которых может запускать цикл реактивного обновления и повторный рендеринг компонента; кроме того, их можно считывать из атрибутов или записывать в них.

class MyElement extends LitElement {
  @property()
  name?: string;
}
class MyElement extends LitElement {
  static properties = {
    name: {},
  };
}

Lit управляет реактивными свойствами и соответствующими им атрибутами. В частности:

  • Реактивные обновления. Lit генерирует пару методов чтения и записи для каждого реактивного свойства. При изменении реактивного свойства компонент планирует обновление.
  • Обработка атрибутов. По умолчанию Lit настраивает наблюдаемый атрибут, соответствующий свойству, и обновляет свойство при изменении атрибута. Значения свойств также можно, при необходимости, отражать обратно в атрибут.
  • Свойства суперкласса. Lit автоматически применяет параметры свойств, объявленные суперклассом. Повторно объявлять свойства не нужно, если только вы не хотите изменить параметры.
  • Обновление элемента. Если компонент Lit определён после того, как элемент уже добавлен в DOM, Lit обрабатывает логику обновления элемента, гарантируя, что любые свойства, заданные до его обновления, вызовут правильные реактивные побочные эффекты после обновления элемента.

Публичные свойства и внутреннее состояние

Публичные свойства входят в публичный API компонента. Как правило, публичные свойства — особенно публичные реактивные свойства — следует считать входными данными.

Компонент не должен изменять собственные публичные свойства, кроме как в ответ на ввод пользователя. Например, компонент меню может иметь публичное свойство selected, которому владелец элемента может задать начальное значение, а сам компонент обновляет его, когда пользователь выбирает пункт. В таких случаях компонент должен отправить событие, чтобы сообщить владельцу компонента об изменении свойства selected. Подробнее см. в разделе Отправка событий.

Lit также поддерживает внутреннее реактивное состояние. Внутреннее реактивное состояние — это реактивные свойства, которые не входят в API компонента. У этих свойств нет соответствующего атрибута, и в TypeScript они обычно помечаются как защищённые или приватные.

@state()
private _counter = 0;
static properties = {
  _counter: {state: true}
};

constructor() {
  super();
  this._counter = 0;
}

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

Как и изменение публичных реактивных свойств, обновление внутреннего реактивного состояния запускает цикл обновления. Подробнее см. в разделе Внутреннее реактивное состояние.

Публичные реактивные свойства

Объявляйте публичные реактивные свойства элемента с помощью декораторов или статического поля properties.

В обоих случаях можно передать объект параметров для настройки возможностей свойства.

Объявление свойств с помощью декораторов

Используйте декоратор @property вместе с объявлением поля класса, чтобы объявить реактивное свойство.

class MyElement extends LitElement {
  @property({type: String})
  mode?: string;

  @property({attribute: false})
  data = {};
}

Аргумент декораторов @property — это объект параметров. Если аргумент не указан, это равнозначно заданию значений по умолчанию для всех параметров.

Использование декораторов. Декораторы — это предлагаемая возможность JavaScript, поэтому для их использования потребуется компилятор, например Babel или компилятор TypeScript. Подробнее см. в разделе Включение декораторов.

Объявление свойств в статическом поле класса properties

Чтобы объявить свойства в статическом поле класса properties:

class MyElement extends LitElement {
  static properties = {
    mode: {type: String},
    data: {attribute: false},
  };

  constructor() {
    super();
    this.data = {};
  }
}

Пустой объект параметров равнозначен заданию значений по умолчанию для всех параметров.

Как избежать проблем с полями класса при объявлении свойств

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

class MyElement extends LitElement {
  static properties = {foo: {type: String}}
  foo = 'Default'; // ❌ this will make `foo` not reactive
}

В JavaScript при объявлении реактивных свойств нельзя использовать поля класса. Вместо этого свойства нужно инициализировать в конструкторе элемента:

class MyElement extends LitElement {
  static properties = {
    foo: {type: String}
  }
  constructor() {
    super();
    this.foo = 'Default';
  }
}

Кроме того, для объявления реактивных свойств можно использовать стандартные декораторы с Babel.

class MyElement extends LitElement {
  @property()
  accessor foo = 'Default';
}

В TypeScript для объявления реактивных свойств можно использовать поля класса, если вы применяете один из следующих подходов:

  • Установите параметр компилятора useDefineForClassFields в значение false. Это уже рекомендуется при использовании декораторов с TypeScript.
// tsconfig.json
{
  "compilerOptions": {
    "experimentalDecorators": true, // If using decorators
    "useDefineForClassFields": false,
  }
}
class MyElement extends LitElement {
  static properties = {foo: {type: String}}
  foo = 'Default';

  @property()
  bar = 'Default';
}
  • Добавьте к полю ключевое слово declare, а инициализатор поля разместите в конструкторе.
class MyElement extends LitElement {
  declare foo: string;
  static properties = {foo: {type: String}}
  constructor() {
    super();
    this.foo = 'Default';
  }
}
  • Добавьте к полю ключевое слово accessor, чтобы использовать автоматические аксессоры.
class MyElement extends LitElement {
  static properties = {foo: {type: String}}
  accessor foo = 'Default';

  @property()
  accessor bar = 'Default';
}

Параметры свойств

Объект параметров может содержать следующие свойства:

attribute

Указывает, связан ли атрибут со свойством, либо задаёт пользовательское имя связанного атрибута. Значение по умолчанию: true. Если attribute равно false, параметры converter, reflect и type игнорируются. Подробнее см. в разделе Задание имени атрибута.

converter

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

hasChanged

Функция, вызываемая при каждом задании свойства, чтобы определить, изменилось ли оно и следует ли запускать обновление. Если функция не указана, LitElement использует проверку строгого неравенства (newValue !== oldValue), чтобы определить, изменилось ли значение свойства. Подробнее см. в разделе Настройка обнаружения изменений.

noAccessor

Задайте значение true, чтобы не генерировать аксессоры свойств по умолчанию. Этот параметр редко бывает необходим. Значение по умолчанию: false. Подробнее см. в разделе Запрет генерации аксессора свойства в Lit.

reflect

Указывает, отражается ли значение свойства обратно в связанный атрибут. Значение по умолчанию: false. Подробнее см. в разделе Включение отражения атрибутов.

state

Задайте значение true, чтобы объявить свойство как внутреннее реактивное состояние. Внутреннее реактивное состояние запускает обновления так же, как публичные реактивные свойства, но Lit не генерирует для него атрибут, а пользователям не следует обращаться к нему извне компонента. Эквивалентно использованию декоратора @state. Значение по умолчанию: false. Подробнее см. в разделе Внутреннее реактивное состояние.

type

При преобразовании атрибута со строковым значением в свойство преобразователь атрибутов Lit по умолчанию преобразует строку в указанный тип, и наоборот — при отражении свойства в атрибут. Если задано значение converter, это поле передаётся преобразователю. Если type не указано, преобразователь по умолчанию считает его значением type: String. См. раздел Использование преобразователя по умолчанию.

При использовании TypeScript это поле обычно должно соответствовать объявленному типу TypeScript для данного поля. Однако параметр type используется средой выполнения Lit для сериализации и десериализации строк и не должен рассматриваться как механизм проверки типов.

useDefault

Задайте значение true, чтобы предотвратить первоначальное отражение атрибута для значения по умолчанию, если для reflect задано значение true, а также чтобы сбрасывать свойство до значения по умолчанию при удалении соответствующего атрибута.

Значение по умолчанию — это начальное значение свойства, заданное в конструкторе или с помощью автоматического аксессора. Это значение сохраняется в памяти, поэтому для свойств Object/Array непримитивных типов рекомендуется не задавать useDefault: true. Подробнее см. разделы Включение отражения атрибутов и Рекомендации по отражению атрибутов.

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

Внутреннее реактивное состояние

Внутреннее реактивное состояние — это реактивные свойства, которые не входят в публичный API компонента. У этих свойств состояния нет соответствующих атрибутов, и они не предназначены для использования извне компонента. Внутреннее реактивное состояние должен задавать сам компонент.

Используйте декоратор @state, чтобы объявить внутреннее реактивное состояние:

@state()
protected _active = false;

При использовании статического поля класса properties внутреннее реактивное состояние можно объявить с помощью параметра state: true.

static properties = {
  _active: {state: true}
};

constructor() {
  this._active = false;
}

Не следует обращаться к внутреннему реактивному состоянию извне компонента. В TypeScript эти свойства следует помечать как приватные или защищённые. Мы также рекомендуем использовать соглашение, например начальное подчёркивание (_), чтобы пользователям JavaScript было проще распознавать приватные или защищённые свойства.

Внутреннее реактивное состояние работает так же, как публичные реактивные свойства, но с этим свойством не связан атрибут. Для внутреннего реактивного состояния можно указать только функцию hasChanged.

Декоратор @state также может служить подсказкой для минификатора кода о том, что имя свойства можно изменить при минификации.

Что происходит при изменении свойств

Изменение свойства может запустить цикл реактивного обновления, в результате которого шаблон компонента рендерится заново.

При изменении свойства происходит следующая последовательность действий:

  1. Вызывается метод записи свойства.
  2. Метод записи вызывает метод компонента requestUpdate.
  3. Сравниваются старое и новое значения свойства.
    • По умолчанию Lit использует проверку строгого неравенства, чтобы определить, изменилось ли значение (то есть newValue !== oldValue).
    • Если для свойства задана функция hasChanged, она вызывается со старым и новым значениями свойства.
  4. Если обнаружено изменение свойства, асинхронно планируется обновление. Если обновление уже запланировано, выполняется только одно обновление.
  5. Вызывается метод компонента update: он отражает изменённые свойства в атрибутах и повторно рендерит шаблоны компонента.

Обратите внимание: изменение объекта или свойства-массива не запускает обновление, поскольку сам объект не изменился. Подробнее см. в разделе Изменение объектов и массивов.

Существует множество способов подключиться к циклу реактивного обновления и изменить его. Подробнее см. в разделе Цикл реактивного обновления.

Подробнее об обнаружении изменений свойств см. в разделе Настройка обнаружения изменений.

Изменение объектов и массивов

Изменение объекта или массива не меняет ссылку на объект, поэтому обновление не запускается. Обрабатывать свойства-объекты и свойства-массивы можно одним из двух способов:

  • Подход с неизменяемыми данными. Считайте объекты и массивы неизменяемыми. Например, чтобы удалить элемент из myArray, создайте новый массив:

    this.myArray = this.myArray.filter((_, i) => i !== indexToRemove);

    Хотя этот пример прост, для управления неизменяемыми данными часто удобно использовать библиотеку, например Immer. Это помогает избежать сложного шаблонного кода при задании глубоко вложенных объектов.

  • Запуск обновления вручную. Измените данные и вызовите requestUpdate(), чтобы запустить обновление напрямую. Например:

    this.myArray.splice(indexToRemove, 1);
    this.requestUpdate();

    Если вызвать requestUpdate() без аргументов, будет запланировано обновление без вызова функции hasChanged(). Однако учтите, что requestUpdate() обновляет только текущий компонент. То есть если компонент использует приведённый выше код и передаёт this.myArray вложенному компоненту, вложенный компонент обнаружит, что ссылка на массив не изменилась, и не обновится.

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

Прямое изменение данных и вызов requestUpdate() следует считать продвинутым сценарием использования. В этом случае вам (или другой системе) нужно определить все компоненты, использующие изменённые данные, и вызвать requestUpdate() для каждого из них. Если эти компоненты расположены в разных частях приложения, управлять ими становится сложно. Если делать это ненадёжно, можно изменить объект, отображаемый в двух частях приложения, но обновится только одна из них.

В простых случаях, когда известно, что определённые данные используются только в одном компоненте, можно безопасно изменить данные и вызвать requestUpdate().

Атрибуты

Свойства удобны для получения данных JavaScript в качестве входных данных, а атрибуты — это стандартный способ настройки элементов из разметки средствами HTML, без необходимости использовать JavaScript для задания свойств. Наличие как интерфейса свойств, так и интерфейса атрибутов для реактивных свойств — важное преимущество компонентов Lit, благодаря которому они полезны в самых разных средах, в том числе при отображении без клиентского шаблонизатора, например на статических HTML-страницах, размещённых в CMS.

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

Свойства элементов могут иметь любой тип, тогда как атрибуты всегда являются строками. Это влияет на наблюдаемые атрибуты и отражаемые атрибуты нестроковых свойств:

  • Чтобы наблюдать за атрибутом (задать свойство по атрибуту), значение атрибута нужно преобразовать из строки в тип свойства.

  • Чтобы отражать атрибут (задать атрибут по свойству), значение свойства нужно преобразовать в строку.

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

Задание имени атрибута

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

// observed attribute name is "myvalue"
@property({ type: Number })
myValue = 0;
// observed attribute name is "myvalue"
static properties = {
  myValue: { type: Number },
};

constructor() {
  super();
  this.myValue = 0;
}

Чтобы создать наблюдаемый атрибут с другим именем, задайте для attribute строку:

// Observed attribute will be called my-name
@property({ attribute: 'my-name' })
myName = 'Ogden';
// Observed attribute will be called my-name
static properties = {
  myName: { attribute: 'my-name' },
};

constructor() {
  super();
  this.myName = 'Ogden'
}

Чтобы не создавать наблюдаемый атрибут для свойства, задайте для attribute значение false. Свойство не будет инициализироваться атрибутами в разметке, а изменения атрибута не будут на него влиять.

// No observed attribute for this property
@property({ attribute: false })
myData = {};
// No observed attribute for this property
static properties = {
  myData: { attribute: false },
};

constructor() {
  super();
  this.myData = {};
}

У внутреннего реактивного состояния никогда нет связанного атрибута.

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

<my-element myvalue="99"></my-element>

Использование преобразователя по умолчанию

В Lit есть преобразователь по умолчанию, который обрабатывает типы свойств String, Number, Boolean, Array и Object.

Чтобы использовать преобразователь по умолчанию, укажите параметр type в объявлении свойства:

// Use the default converter
@property({ type: Number })
count = 0;
// Use the default converter
static properties = {
  count: { type: Number },
};

constructor() {
  super();
  this.count = 0;
}

Если для свойства не указан ни тип, ни пользовательский преобразователь, оно ведёт себя так, как если бы было указано type: String.

В таблицах ниже показано, как преобразователь по умолчанию выполняет преобразование для каждого типа.

Из атрибута в свойство

Тип Преобразование
String Если у элемента есть соответствующий атрибут, свойству присваивается значение атрибута.
Number Если у элемента есть соответствующий атрибут, свойству присваивается значение Number(attributeValue).
Boolean Если у элемента есть соответствующий атрибут, свойству присваивается значение true.
Если атрибута нет, свойству присваивается значение false.
Object, Array Если у элемента есть соответствующий атрибут, значению свойства присваивается JSON.parse(attributeValue).

Во всех случаях, кроме Boolean, если у элемента нет соответствующего атрибута, свойство сохраняет значение по умолчанию или получает значение undefined, если значение по умолчанию не задано.

Из свойства в атрибут

Тип Преобразование
String, Number Если свойство определено и не равно null, атрибуту присваивается значение свойства.
Если свойство равно null или undefined, атрибут удаляется.
Boolean Если значение свойства истинно, создаётся атрибут, которому присваивается пустая строка.
Если значение свойства ложно, атрибут удаляется.
Object, Array Если свойство определено и не равно null, атрибуту присваивается JSON.stringify(propertyValue).
Если свойство равно null или undefined, атрибут удаляется.

Предоставление пользовательского преобразователя

Пользовательский преобразователь свойств можно указать в объявлении свойства с помощью параметра converter:

myProp: {
  converter: // Custom property converter
}

converter может быть объектом или функцией. Если это объект, он может содержать ключи fromAttribute и toAttribute:

prop1: {
  converter: {
    fromAttribute: (value, type) => {
      // `value` is a string
      // Convert it to a value of type `type` and return it
    },
    toAttribute: (value, type) => {
      // `value` is of type `type`
      // Convert it to a string and return it
    }
  }
}

Если converter — это функция, она используется вместо fromAttribute:

myProp: {
  converter: (value, type) => {
    // `value` is a string
    // Convert it to a value of type `type` and return it
  }
}

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

Если toAttribute возвращает null или undefined, атрибут удаляется.

Логические атрибуты

Чтобы логическое свойство можно было настраивать с помощью атрибута, его значением по умолчанию должно быть false. Если его значение по умолчанию — true, задать для него значение false из разметки невозможно, поскольку наличие атрибута, независимо от того, есть ли у него значение, равнозначно значению true. Таково стандартное поведение атрибутов в веб-платформе.

Если такое поведение не подходит для вашего случая, есть несколько вариантов:

  • Измените имя свойства так, чтобы его значение по умолчанию было false. Например, веб-платформа использует атрибут disabled (значение по умолчанию — false), а не enabled.

  • Вместо этого используйте атрибут со строковым или числовым значением.

Включение отражения атрибутов

Установка reflect в значение true настраивает свойство так, что при каждом его изменении значение отражается в соответствующем атрибуте. Отражаемые атрибуты полезны для сериализации состояния элемента, а также потому, что они доступны CSS и API DOM, например querySelector.

Установка useDefault в значение true предотвращает первоначальное отражение значения свойства по умолчанию в соответствующий атрибут. Все последующие изменения отражаются; при удалении атрибута свойству присваивается значение по умолчанию.

Это соответствует поведению веб-платформы для таких атрибутов, как id. Значение свойства элемента id по умолчанию — '' (пустая строка), и изначально у него нет атрибута id. Однако если задано свойство id (даже пустой строкой), отражается соответствующий атрибут id. Если атрибут id удаляется, свойству элемента id снова присваивается его начальное значение ''.

Например:

// Value of property "active" will reflect to attribute "active"
active: {reflect: true}
// Value of property "variant" will reflect except that the "variant"
// attribute will not be iniitally set to the property's default value.
variant: {reflect: true, useDefault: true}

При изменении свойства Lit задаёт значение соответствующего атрибута, как описано в разделах Использование преобразователя по умолчанию и Предоставление пользовательского преобразователя.

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

Рекомендации по отражению атрибутов

Чтобы элементы работали ожидаемым образом и эффективно, при отражении атрибутов старайтесь следовать этим рекомендациям:

  • Атрибуты обычно следует считать входными данными, которые владелец передаёт элементу, а не данными, которыми управляет сам элемент. Поэтому отражать свойства в атрибуты следует выборочно. По возможности вместо этого рассмотрите возможность использования псевдоселектора :state и модели объектов доступности.

  • При отражении свойств обычно также следует задавать useDefault: true: это предотвращает неожиданное появление атрибутов, которые пользователь не задавал, и помогает соответствовать ожидаемому поведению платформы.

  • Не рекомендуется отражать свойства объектного типа или типа массива. Это может привести к сериализации больших объектов в DOM, снижению производительности и избыточному потреблению памяти при использовании useDefault.

  • Декоратор свойства не изменяет значения, присвоенные реактивному свойству, что считается хорошей практикой для пользовательских аксессоров. Например, иногда нативные элементы ограничивают свойства определёнными допустимыми значениями и при присвоении недопустимого значения устанавливают вместо него значение по умолчанию. useDefault: true этого не делает — он лишь восстанавливает значение по умолчанию при удалении атрибута. Если нужно изменять значение свойства при присваивании, определите и декорируйте пользовательский метод записи свойства.

Пользовательские аксессоры свойств

По умолчанию LitElement генерирует пару методов чтения и записи для всех реактивных свойств. Метод записи вызывается при каждом присваивании свойства:

// Declare a property
@property()
greeting: string = 'Hello';
...
// Later, set the property
this.greeting = 'Hola'; // invokes greeting's generated property accessor
// Declare a property
static properties = {
  greeting: {},
}
constructor() {
  this.super();
  this.greeting = 'Hello';
}
...
// Later, set the property
this.greeting = 'Hola'; // invokes greeting's generated property accessor

Сгенерированные аксессоры автоматически вызывают requestUpdate(), запуская обновление, если оно ещё не началось.

Создание пользовательских аксессоров свойств

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

private _prop = 0;

@property()
set prop(val: number) {
  this._prop = Math.floor(val);
}

get prop() { return this._prop; }
static properties = {
  prop: {},
};

_prop = 0;

set prop(val) {
  this._prop = Math.floor(val);
}

get prop() { return this._prop; }

Чтобы использовать пользовательские аксессоры свойств с декораторами @property или @state, поместите декоратор над методом записи, как показано выше. Декорированные методы записи @property или @state вызывают requestUpdate().

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

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

Запрет генерации аксессора свойства в Lit

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

Чтобы Lit не генерировал аксессор свойства, который перезапишет определённый в суперклассе аксессор, задайте для noAccessor значение true в объявлении свойства:

static properties = {
  myProp: { type: Number, noAccessor: true }
};

При определении собственных аксессоров задавать noAccessor не нужно.

Настройка обнаружения изменений

У всех реактивных свойств есть функция hasChanged(), которая вызывается при задании свойства.

hasChanged сравнивает старое и новое значения свойства и определяет, изменилось ли оно. Если hasChanged() возвращает true, Lit запускает обновление элемента, если оно ещё не запланировано. Подробнее об обновлениях см. в разделе Цикл реактивного обновления.

Реализация hasChanged() по умолчанию использует сравнение строгого неравенства: hasChanged() возвращает true, если newVal !== oldVal.

Чтобы настроить hasChanged() для свойства, укажите его как параметр свойства:

@property({
  hasChanged(newVal: string, oldVal: string) {
    return newVal?.toLowerCase() !== oldVal?.toLowerCase();
  }
})
myProp: string | undefined;
static properties = {
  myProp: {
    hasChanged(newVal, oldVal) {
      return newVal?.toLowerCase() !== oldVal?.toLowerCase();
    }
  }
};

В следующем примере hasChanged() возвращает true только для нечётных значений.

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

© Google LLC
Licensed under the Creative Commons Attribution 3.0 Unported License.
https://lit.dev/docs/components/properties/

Spec-Zone.ru

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