Встроенные компоненты
PreffX поставляется с четырьмя встроенными компонентами, доступными через второй аргумент (utils) каждого компонента. Они решают типовые задачи UI без дополнительных зависимостей.
| Компонент | Назначение | Доступность |
|---|---|---|
For | Рендеринг списков с реактивными элементами | utils.For |
Catch | Перехват и обработка ошибок рендеринга | utils.Catch |
Portal | Рендеринг дочерних элементов в другом DOM-узле | utils.Portal |
Defer | Показ заглушки до готовности значения | utils.Defer |
For
Реактивный рендерер списков. Принимает сигнал элементов (или обычный массив) и перерендеривает только изменившиеся элементы. Поддерживает необязательный fallback, показываемый при пустом списке.
Сигнатура типа
const For: PC<{
items: Signal<any[]>;
callback: (item: any) => any;
fallback?: any;
}>Свойства
| Свойство | Тип | Обязательно | Описание |
|---|---|---|---|
items | Signal<any[]> | Да | Сигнал, содержащий массив элементов. Когда сигнал меняется, For эффективно согласует список — добавленные элементы рендерятся, удалённые очищаются. |
callback | (item: any) => any | Да | Функция, вызываемая для каждого элемента. Возвращает JSX для рендеринга этого элемента. Возвращаемое значение кешируется и пересчитывается только при изменении самого элемента. |
fallback | any | Нет | Содержимое, рендерящееся при пустом массиве. |
Использование
import type { PC } from 'preffx';
type Item = { id: number; name: string };
const App: PC = (_, { signal, For }) => {
const items = signal<Item[]>([
{ id: 1, name: 'Alpha' },
{ id: 2, name: 'Beta' },
{ id: 3, name: 'Gamma' },
]);
return (
<ul>
<For
items={items}
callback={(item: Item) => (
<li key={item.id}>{item.name}</li>
)}
fallback={<li>Нет элементов</li>}
/>
</ul>
);
};Реактивные обновления
При изменении массива в сигнале DOM обновляется автоматически:
// Добавление элемента
items.value = [...items.value, { id: 4, name: 'Delta' }];
// Удаление элемента
items.value = items.value.filter((i) => i.id !== 2);
// Замена всех элементов
items.value = [];
// -> показывается fallback "Нет элементов"Сигналы-элементы внутри массива
Каждый элемент массива сам может быть сигналом. Когда значение сигнала элемента меняется, пересчитывается только вывод этого элемента:
const items = signal([
signal({ id: 1, name: 'Alpha' }),
signal({ id: 2, name: 'Beta' }),
]);
// Обновление второго элемента — перерендеривается только "Beta"
items.value[1].value = { id: 2, name: 'BETA' };Catch
Перехватывает ошибки, выброшенные (или возвращённые) дочерними компонентами, и вместо них рендерит запасное содержимое. Вдохновлён React Error Boundaries.
Сигнатура типа
const Catch: PC<{
fallback?: any;
children?: any | any[];
}>Свойства
| Свойство | Тип | Обязательно | Описание |
|---|---|---|---|
children | any | any[] | Нет | Дочерние элементы для рендеринга. Если любой дочерний элемент является (или возвращает) экземпляр Error, ошибка перехватывается. |
fallback | any | Нет | Содержимое, показываемое при перехвате ошибок. Может быть статическим значением или функциональным компонентом, получающим { errors, ...extraProps }. |
Статическое запасное содержимое
import type { PC } from 'preffx';
const App: PC = (_, { Catch }) => {
return (
<div>
<Catch fallback={<div>Что-то пошло не так</div>}>
<MaybeBrokenComponent />
</Catch>
</div>
);
};Функциональный компонент как запасной
Функция запасного варианта получает errors (массив перехваченных ошибок) плюс любые дополнительные свойства, переданные в <Catch>:
const App: PC = (_, { Catch }) => {
return (
<Catch
fallback={({ errors }) => (
<div class="error-fallback">
<h3>Произошло {errors.length} ошибка(и)</h3>
</div>
)}
>
<UnstableComponent />
</Catch>
);
};Передача дополнительных свойств в запасной компонент
const App: PC = (_, { Catch }) => {
return (
<Catch
fallback={({ errors, severity }) => (
<div class={severity}>
Ошибок: {errors.length}
</div>
)}
severity="critical"
>
<UnstableComponent />
</Catch>
);
};Как обнаруживаются ошибки
Catch рекурсивно сканирует все дочерние элементы (используя flat(Infinity)). Любой дочерний элемент, являющийся экземпляром Error (через instanceof Error), запускает запасное содержимое.
Portal
Рендерит дочерние элементы в другой DOM-узел вне корневого элемента компонента. Полезно для модальных окон, подсказок, выпадающих списков и уведомлений.
Сигнатура типа
const Portal: PC<{
root: HTMLElement;
children?: any | any[];
}>Свойства
| Свойство | Тип | Обязательно | Описание |
|---|---|---|---|
root | HTMLElement | Да | Целевой DOM-элемент, в который должны быть рендерены дочерние элементы. |
children | any | any[] | Нет | Содержимое для рендеринга внутри корневого элемента портала. |
Базовое использование
import type { PC } from 'preffx';
const Modal: PC = (_, { Portal }) => {
const portalRoot = document.getElementById('modal-root')!;
return (
<Portal root={portalRoot}>
<div class="modal-overlay">
<div class="modal-content">Привет из портала!</div>
</div>
</Portal>
);
};Реактивное содержимое внутри портала
Дочерние элементы внутри портала полностью реактивны — сигналы обновляют их автоматически:
const Notification: PC<{ message: string }> = (
{ message },
{ signal, Portal },
) => {
const count = signal(0);
return (
<div>
<Portal root={document.getElementById('toast-root')!}>
<span id="notification-count">{count}</span>
</Portal>
<button onClick={() => (count.value += 1)}>+</button>
</div>
);
};Очистка
Когда родительский компонент уничтожается, Portal автоматически очищает свои дочерние элементы и корень портала через root.replaceChildren().
Без корневого элемента
Если root равен null или undefined, Portal ничего не рендерит:
<Portal root={null} children={<span>Не появится</span>} />
// ничего не рендеритDefer
Показывает начальное запасное значение, пока готовится фактическое значение (например, ожидание разрешения Promise или пока сигнал не получит не-promise значение).
Сигнатура типа
const Defer: PC<{
initial?: any;
value: Signal<any>;
}>Свойства
| Свойство | Тип | Обязательно | Описание |
|---|---|---|---|
initial | any | Нет | Запасное содержимое, рендерящееся пока value содержит Promise или ещё не готово. |
value | Signal<any> | Да | Сигнал, значение которого отслеживается. Когда разрешённое значение сигнала не является Promise, оно заменяет содержимое initial. |
Отложенный Promise
import type { PC } from 'preffx';
const SlowData: PC = (_, { signal, Defer }) => {
const data = signal(
new Promise<string>((resolve) =>
setTimeout(() => resolve('Загружено!'), 2000)
)
);
return (
<Defer
initial={<div>Загрузка...</div>}
value={data}
/>
);
};Последовательность рендеринга:
- Немедленно показывает
Загрузка... - Через 2 секунды, когда Promise разрешается, показывает
Загружено!
Отложенное обычное (не-promise) значение
Если сигнал уже содержит обычное значение (не Promise), Defer рендерит его немедленно — initial пропускается:
<Defer
initial="Ожидание..."
value={signal('Реальный контент')}
/>
// Немедленно рендерит "Реальный контент"Реактивное переключение с Promise на обычное значение
Когда сигнал меняется с Promise на обычное значение, Defer переключается с заглушки на реальный контент:
const DynamicDefer: PC = (_, { signal, effect, Defer, onMount }) => {
const value = signal(new Promise(() => {})); // ожидающий Promise
onMount(() => {
setTimeout(() => {
value.value = 'Наконец-то разрешилось!';
}, 1000);
});
return <Defer initial="Загрузка..." value={value} />;
};Массив дочерних элементов через Defer
Defer работает с любым содержимым, включая массивы JSX-узлов:
const items = signal([
<span id="a">Alpha</span>,
<span id="b">Beta</span>,
]);
return (
<Defer
initial={[<div>Загрузка...</div>]}
value={items}
/>
);
// Немедленно рендерит <span>Alpha</span><span>Beta</span>Очистка
Когда родительский компонент уничтожается, Defer автоматически очищает своё внутреннее отслеживание сигналов.