Атрибуты, свойства и состояние
Глава разводит три понятия — атрибут, свойство и состояние — и показывает, как компонент объявляет и читает свойства. Она нужна, чтобы компонент вёл себя одинаково при любой разметке.
Три разных понятия
- Атрибут — строка в HTML:
count="2". - Свойство — значение, которое компонент объявил и читает с приведением типа.
- Состояние — текущие данные, от которых зависит показ: открыто ли меню, какое значение выбрано.
Они связаны только тогда, когда компонент явно их связывает.
Простые начальные значения задают атрибутами; объекты и массивы передают через свойства или методы, которые объявил конкретный компонент.
Объявление свойств
Пример показывает основные ключи объявления свойства:
static get props() {
return {
size: {
type: String,
default: '1',
values: ['1/3', '1/2', '1', '2', '3'],
valueAliases: { '1/2': ['small'], '1': ['medium'], '2': ['large'] },
},
appearance: { type: String, default: 'default', aliases: ['type'] },
templateName: { type: String, attribute: 'template' },
items: { type: Array, attribute: false },
};
}
| Ключ | Значение |
|---|---|
type |
String, Number, Boolean, Array, Object — к чему приводить строку атрибута. Без type тип берётся из значения default, а без него — String |
default |
Значение, когда атрибута нет. Это то, что компонент действительно делает без атрибута |
values |
Допустимые значения строкового свойства; прочие заменяются значением по умолчанию (без него — первым из списка) |
attribute |
Имя атрибута. По умолчанию — имя свойства в kebab-case: decreaseLabel читается из decrease-label, templateName — из template-name. Задайте его, если имя другое (templateName → template); false — свойство без атрибута |
aliases |
Дополнительные имена атрибута, принимаемые для совместимости (см. Имена для совместимости) |
valueAliases |
Дополнительные имена значений, принимаемые для совместимости (см. Имена для совместимости) |
parser |
Функция (raw, fallback, element), которая вычисляет значение из строки атрибута вместо приведения по type |
Базовый класс наблюдает объявленные атрибуты (и всегда — template,
root-class, root-style, style) и перерисовывает компонент при их
изменении.
Пример
Счётчик объявляет count: { type: Number, default: 0 }. Метод
getProp('count') читает значение с приведением типа, setAttribute('count', '3')
запускает наблюдаемое обновление в ближайшей микрозадаче (см.
Жизненный цикл → Этапы), а начальное count="1" в разметке
перекрывает значение по умолчанию.
<project-counter count="1"></project-counter>
<p id="counter-log" role="status">Нажмите кнопку: ниже появятся данные события.</p>
async function start() {
if (typeof window.SF?.loadSmartBase !== 'function') {
await new Promise((resolve, reject) => {
const ready = () => { clearTimeout(timeout); resolve(); };
const timeout = setTimeout(() => { window.removeEventListener('sf-loader-init', ready); reject(new Error('Loader не запустился')); }, 15000);
window.addEventListener('sf-loader-init', ready, { once: true });
});
}
await SF.loadSmartBase();
const { SfBaseElement, html } = SF.smart;
// The component's own words: English in `en`, Russian in `ru`.
const TEXT = {
en: { count: 'Count', add: 'Add' },
ru: { count: 'Количество', add: 'Добавить' },
};
class ProjectCounter extends SfBaseElement {
static get props() {
// A label prop defaults to empty: the English word lives in the dictionary.
return { count: { type: Number, default: 0 }, addLabel: { default: '' } };
}
templateContext() {
// Keeps component, rootClass, rootStyle and every declared prop.
return this.createPropsTemplateContext({
countText: SF.pageText(TEXT, 'count', this),
addText: this.getProp('addLabel') || SF.pageText(TEXT, 'add', this),
});
}
increment() {
const count = this.getProp('count') + 1;
this.setAttribute('count', String(count));
this.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true, detail: { value: count } }));
}
template() {
const { count, countText, addText, rootClass } = this.templateContext();
return html`<div class="flex gap-2 items-cross-center ${rootClass}">
<output aria-live="polite">${countText}: ${count}</output>
<button type="button"
class="sf-button sf-button--outline sf-button--on-surface sf-button--size-1/2"
@click=${() => this.increment()}>
<span class="sf-button-text-container">${addText}</span>
</button>
</div>`;
}
}
ProjectCounter.define('project-counter');
document.querySelector('project-counter').addEventListener('change', event => {
document.querySelector('#counter-log').textContent = JSON.stringify({ value: event.detail.value });
});
}
start().catch(error => { document.querySelector('#counter-log').textContent = error.message; });
Как компонент читает свойство
this.getProp('count')— значение с приведением типа и значением по умолчанию;this.getEnumAttr(name, allowed, fallback, aliases)— значение атрибута из допустимого спискаallowed, иначеfallback;aliases— дополнительные имена атрибута. Вторым аргументом можно передать объект настроек, тогда работают и имена значений:this.getEnumAttr('size', { values: ['1/2', '1'], fallback: '1', valueAliases: { '1/2': ['small'] } });this.getPropsContext()— объект со всеми объявленными свойствами:{ count: this.getProp('count'), … }; его же базовый класс кладёт в контекст шаблона (см. Шаблон и templateContext);- геттер, который компонент определяет сам, если значение нужно вычислить.
static props не создаёт полей на экземпляре. Чтение this.count
напрямую, без геттера, даёт undefined. Это правило проверяется
автоматически.
Имена для совместимости
Ключи aliases и valueAliases позволяют компоненту принимать дополнительные
имена атрибутов и значений, не ломая существующую разметку. Если заданы
каноническое и дополнительное имя, побеждает каноническое; наблюдаются оба.
Каждое использование дополнительного имени записывается в SF.deprecations.
Например, valueAliases: { '1/2': ['small'] } принимает size="small" и
отвечает ступенью 1/2. Так Framework переименовывает вход, не ломая
существующую разметку; правило имён — см. Канонические имена и совместимость,
механизм совместимости целиком — Жизненный цикл изменений.
Приведение типов
Булевы значения
Свойство с type: Boolean базовый класс читает из атрибута так:
| Атрибут | Значение |
|---|---|
| Атрибута нет | Значение по умолчанию (default), без него — false |
Атрибут без значения или пустой (search, search="") |
true |
"true", "1", "yes", "on", а также "checked", "disabled", "download", "indeterminate", "multiple" (без учёта регистра) |
true |
Любое другое значение, в том числе "false", "0", "no" |
false |
Поэтому search="false" выключает свойство, а отсутствие атрибута даёт
значение по умолчанию: у свойства со значением по умолчанию true это не
одно и то же. Для блокировки и загрузки используйте объявленные disabled и
loading, а не стили, которые только имитируют блокировку.
Числа
Свойство с type: Number базовый класс читает из атрибута так:
| Атрибут | Значение |
|---|---|
| Атрибута нет | Значение по умолчанию как есть, в том числе null; без default — 0 |
Число ("3", "-1.5") |
Это число |
Пустая строка (count="") |
0 |
Не число ("abc") |
Значение по умолчанию, а если оно null, 0 или не задано, — 0 |
Поэтому у свойства с default: null отсутствие атрибута даёт null, а
нечисловое значение — 0. Если нужно отличать «не задано» от ошибки ввода,
задайте parser.
Массивы и объекты
Свойство с type: Array или type: Object читает атрибут как JSON. Если JSON
не разбирается, компонент пишет предупреждение в консоль и берёт значение по
умолчанию.
Значение по умолчанию — это обещание
Правила:
- значение по умолчанию в манифесте обязательно совпадает с тем, что компонент делает без атрибута;
meta.mjsобязательно совпадает с манифестом.
Несовпадение — дефект. Браузерная проверка его находит: компонент рисуется без атрибута, и результат сравнивается с объявленным.
Граница состояния
Состояние компонента — его собственные данные показа: открыта ли панель, какое значение выбрано. Корзина, права и сохранение остаются в проекте; после операции проект передаёт результат компоненту (см. Граница состояния).
Смотрите также
- Шаблон и templateContext — как шаблон получает разрешённые значения.
- Манифест — манифест.
- Проверки — проверки.
- Жизненный цикл изменений — совместимость имён.