Фрагменты интерфейса
Страницу рабочего интерфейса удобно собирать из фрагментов: список файлов,
дерево папок, счётчик уведомлений, карточка записи. Фрагмент — составной
Smart-компонент, поставленный в именованную область страницы, например
content. У него ровно один порт хоста, через который он говорит с
приложением, и опубликованные порты композиции, через которые он говорит с
соседями. Фрагмент интерфейса не страница, не шаблон и не серверная вставка
со своим поведением; не путайте его с фрагментом Recipe — повторно
используемой частью плана сборки (Словарь Recipe). Правила фрагмента задаёт стандарт
simai.interface-fragment 1.0.2.
Как фрагменты связаны
Фрагменты не знают друг о друге:
- фрагмент не вызывает методы другого фрагмента, не обращается к его порту хоста и не ищет его в DOM;
- то, что фрагмент хочет сказать соседям, уходит через его опубликованный выход, а то, что он слушает, приходит на объявленный вход;
- страница — один
layout.scope: фрагменты — его конечные точки, а маршруты между их портами объявляет страница, а не фрагменты; - маршрут соединяет две существующие конечные точки, и типы значений на его концах совпадают (Типы значений маршрутов).
Значение между фрагментами говорит, что и где изменилось, но не несёт
данные. Список файлов отправляет files-moved с идентификаторами и
папками; дерево папок получает его и перечитывает нужные папки через свой
порт хоста со своими правами. Последствия ответов приложения приходят к
фрагментам тем же путём — через хост страницы.
{
"id": "files-page",
"type": "layout.scope",
"props": {"routes": [
{"id": "moved-to-tree",
"from": {"endpoint": "files", "port": "moved"},
"to": {"endpoint": "folders", "port": "moved"}}
]}
}
Хост страницы
Приложение в ответе порта может сообщить последствия (effects):
что ещё изменилось на странице из-за выполненного намерения. Например,
перемещение файлов меняет и список, и дерево папок, и счётчик. Последствия
публикует элемент <sf-page-host> — конечная точка layout.scope, у которой
есть только выходы, по одному на каждый тип значений страницы. Выход назван
типом без версии: record-changed, record-removed, collection-changed,
files-moved, counter, navigation, preference-changed. Хост сам ни
одного фрагмента не вызывает: последствия доставляют маршруты, которые
объявила страница.
<sf-composition-scope data-sf-routes='[{"id": "files-refresh",
"from": {"endpoint": "host", "port": "collection-changed", "value": "collection-changed.v1"},
"to": {"endpoint": "files", "port": "changed", "value": "collection-changed"}}]'>
<sf-page-host data-sf-endpoint="host"></sf-page-host>
<sf-fragment data-sf-fragment="shop.file-list">
<shop-file-list data-sf-endpoint="files"></shop-file-list>
</sf-fragment>
</sf-composition-scope>
Разметку области обычно выводит сервер из документа композиции; здесь она
показана готовой. <sf-page-host> скрыт и входов не имеет. Каждая сторона
маршрута несёт тип в той форме, в какой его объявил порт: выходы хоста
объявлены прежними метками (collection-changed.v1), а список файлов — именем
без версии. Это один тип, и маршрут допустим
(Имя типа).
Последствия — список {type, value}, где type — имя типа без версии
(record-changed); прежняя метка (record-changed.v1) читается до
2026-12-01 и уходит на тот же выход. Их публикует
SF.Composition.publishEffects(effects, {origin}): функция находит хост в
той области sf-composition-scope, где стоит элемент origin (обычно
фрагмент, получивший ответ), а если origin не задан или стоит вне области —
единственный хост страницы.
const answer = await host.raise('files.move', payload, {signal});
const {published, refused} = SF.Composition.publishEffects(answer.effects ?? [], {origin: fileList});
Каждое последствие проверяется по своему типу. Неверное значение или тип,
которого хост не публикует, отклоняется, а остальные всё равно уходят.
Ответ функции — {published, refused}: published — опубликованные типы,
refused — список {type, reason}, где reason — type_not_published,
value_invalid (с message) или no_page_host, если хост не найден или их
несколько. Об отклонённых хост сообщает событием sf-page-host-refused с
detail: {refused}. Тот же publish(effects) есть у самого элемента
<sf-page-host>.
Описание фрагмента
Каждый фрагмент публикует описание — JSON с меткой
"schema": "simai.interface-fragment.v1". В описании объявлено всё, чем
фрагмент обменивается и что ему нужно; необъявленного нет.
{
"schema": "simai.interface-fragment.v1",
"id": "shop.order-list",
"element": "shop-order-list",
"regions": ["content"],
"port": {"standard": "simai.composite-host-port", "version": "1.0.4"},
"outputs": {
"changed": {"value": "record-changed", "summary": "Заказ изменён в списке."}
},
"inputs": {
"removed": {"value": "record-removed", "summary": "Убрать заказ из списка."}
},
"operation": "orders.read",
"url_state": ["status", "page"],
"budget": {"kb": 40, "first_answer_ms": 300}
}
| Поле | Что означает | Правило |
|---|---|---|
schema |
метка формата | simai.interface-fragment.v1 или simai.interface-fragment с "schema_version": 1 |
id |
имя фрагмента | не меньше двух частей через точку из строчных латинских букв, цифр и дефиса: shop.order-list |
element |
тег элемента | имя пользовательского элемента с дефисом: shop-order-list |
regions |
области страницы, где фрагмент может стоять | от одной до восьми уникальных имён: строчная буква, затем до 31 строчной буквы, цифры или дефиса |
port |
стандарт и версия порта хоста | {standard, version}, версия полная: 1.0.4 |
outputs |
выходы | до 16 портов; у каждого value — тип значения из перечня, имя без версии (record-changed) или прежняя метка (record-changed.v1) до 2026-12-01, и summary — описание до 1000 символов |
inputs |
входы | то же, что outputs |
operation |
операция доступа, без которой фрагмент не показывают | слово-метка: строчная буква, затем до 127 символов из строчных букв, цифр, точки, подчёркивания, двоеточия и дефиса |
url_state |
ключи адреса, которыми владеет фрагмент | до 16 уникальных ключей: строчная буква, затем до 63 строчных букв, цифр, подчёркиваний или дефисов |
budget |
бюджет | kb — килобайты сверх общей основы, от 0 до 2048; first_answer_ms — миллисекунды до первого ответа, от 0 до 10000 |
Все поля обязательны. Поле, которого нет в таблице, — ошибка; у портов нет
других ключей, кроме value и summary.
Проверка описания
SF.Composition.checkFragmentDescriptor(descriptor) возвращает список
проблем; пустой список означает, что описание верно. Функция ничего не
исправляет и не приводит. Тот же вызов есть в модуле
distr/core/js/composition/index.mjs, поэтому гейт пакета, страница и тест
читают одно правило. Метка формата лежит в
SF.Composition.FRAGMENT_DESCRIPTOR_SCHEMA, схема 1.2.0 — в поставке ui
(distr/core/contracts/interface-fragment.v1.schema.json).
SF.Composition.checkFragmentDescriptor(descriptor); // []
SF.Composition.checkFragmentDescriptor({
...descriptor,
regions: [],
inputs: {removed: {value: 'files.v9', summary: 'Убрать заказ.'}},
script: 'init.js'
});
// [
// 'script: undeclared',
// 'regions: one to eight region names',
// 'inputs.removed: unknown value type files.v9'
// ]
Гейт пакета проверяет каждое описание до выпуска. Фрагмент без верного описания на страницу не ставится.
Граница фрагмента
Фрагмент на странице оборачивают в <sf-fragment> — границу, которая не
даёт сбою одного фрагмента сломать остальные:
<sf-fragment data-sf-fragment="shop.order-list" load-timeout="10000">
<shop-order-list data-sf-endpoint="orders"></shop-order-list>
</sf-fragment>
Граница заменяет фрагмент на его месте состоянием ошибки
(<sf-empty-state kind="error"> с кнопкой «Повторить»), если:
- Smart-компонент внутри неё упал при отрисовке. Компонент сообщает об этом
событием
sf-component-errorсdetail: {phase, message, element}и атрибутомdata-sf-error; ошибка по-прежнему пишется в консоль; - элемент фрагмента — первый пользовательский элемент внутри границы — не
определился за
load-timeoutмиллисекунд (по умолчанию 10000).
Ушедший фрагмент отключается вместе со своими маршрутами и слушателями, а
остальные фрагменты и их маршруты продолжают работать. Граница получает
data-sf-fragment-state="error" и отправляет sf-fragment-error с
detail: {fragment, phase, message}, где fragment — значение
data-sf-fragment, а phase — render или load. «Повторить» возвращает
исходную разметку фрагмента, он запускается заново, и приходит
sf-fragment-retry с detail: {fragment}. Заголовок ошибки берётся из языка
страницы.
Состояния, доступ и адрес
- Пять состояний. Фрагмент показывает те же состояния, что любой Smart-компонент: загрузка, есть данные, пусто, нет доступа, ошибка. Загрузка — скелетон в своей области, а не пустой ответ (Загрузка данных и скелетоны).
- Сбой не выходит за фрагмент. Фрагмент, упавший при загрузке или отрисовке, заменяется на своём месте состоянием ошибки с повтором (Граница фрагмента).
- Доступ до размещения. Если человеку недоступна операция
operation, фрагмент не ставится на страницу или ставится в состоянии «нет доступа». Элементов управления, которые хост отклонит, он не показывает. - Состояние в адресе. Папка, фильтр, выбранная запись — всё, чем делятся
или к чему возвращаются, живёт в адресе под ключами из
url_state. Два фрагмента одной страницы не владеют одним ключом. - Уход со страницы. Фрагмент, ушедший при замене области (Навигация по областям страницы), отключается: его маршруты, слушатели и таймеры освобождаются.
Бюджет и версии
Бюджет из описания измеряют проверки пакета; фрагмент сверх бюджета не проходит гейт. Область приносит ресурсы своих фрагментов, а ресурс, который уже есть на странице, повторно не запрашивается.
Описание следует версиям порта. Младшая версия только добавляет необязательные выходы, входы или ключи адреса. Удаление, переименование или смена типа значения — новая старшая версия.
Смотрите также
- Составной компонент в макете — составной компонент как тип макета.
- Типы значений маршрутов — перечень типов и правила отказа.
- Навигация по областям страницы — замена области содержимого без полной загрузки.
- Порты хоста и намерения — порт хоста фрагмента.
- Фрагмент интерфейса (
simai.interface-fragment) — требования стандарта.