Skip to content

Встроенные компоненты

PreffX поставляется с четырьмя встроенными компонентами, доступными через второй аргумент (utils) каждого компонента. Они решают типовые задачи UI без дополнительных зависимостей.

КомпонентНазначениеДоступность
ForРендеринг списков с реактивными элементамиutils.For
CatchПерехват и обработка ошибок рендерингаutils.Catch
PortalРендеринг дочерних элементов в другом DOM-узлеutils.Portal
DeferПоказ заглушки до готовности значенияutils.Defer

For

Реактивный рендерер списков. Принимает сигнал элементов (или обычный массив) и перерендеривает только изменившиеся элементы. Поддерживает необязательный fallback, показываемый при пустом списке.

Сигнатура типа

typescript
const For: PC<{
    items: Signal<any[]>;
    callback: (item: any) => any;
    fallback?: any;
}>

Свойства

СвойствоТипОбязательноОписание
itemsSignal<any[]>ДаСигнал, содержащий массив элементов. Когда сигнал меняется, For эффективно согласует список — добавленные элементы рендерятся, удалённые очищаются.
callback(item: any) => anyДаФункция, вызываемая для каждого элемента. Возвращает JSX для рендеринга этого элемента. Возвращаемое значение кешируется и пересчитывается только при изменении самого элемента.
fallbackanyНетСодержимое, рендерящееся при пустом массиве.

Использование

tsx
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 обновляется автоматически:

tsx
// Добавление элемента
items.value = [...items.value, { id: 4, name: 'Delta' }];

// Удаление элемента
items.value = items.value.filter((i) => i.id !== 2);

// Замена всех элементов
items.value = [];
// -> показывается fallback "Нет элементов"

Сигналы-элементы внутри массива

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

tsx
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.

Сигнатура типа

typescript
const Catch: PC<{
    fallback?: any;
    children?: any | any[];
}>

Свойства

СвойствоТипОбязательноОписание
childrenany | any[]НетДочерние элементы для рендеринга. Если любой дочерний элемент является (или возвращает) экземпляр Error, ошибка перехватывается.
fallbackanyНетСодержимое, показываемое при перехвате ошибок. Может быть статическим значением или функциональным компонентом, получающим { errors, ...extraProps }.

Статическое запасное содержимое

tsx
import type { PC } from 'preffx';

const App: PC = (_, { Catch }) => {
    return (
        <div>
            <Catch fallback={<div>Что-то пошло не так</div>}>
                <MaybeBrokenComponent />
            </Catch>
        </div>
    );
};

Функциональный компонент как запасной

Функция запасного варианта получает errors (массив перехваченных ошибок) плюс любые дополнительные свойства, переданные в <Catch>:

tsx
const App: PC = (_, { Catch }) => {
    return (
        <Catch
            fallback={({ errors }) => (
                <div class="error-fallback">
                    <h3>Произошло {errors.length} ошибка(и)</h3>
                </div>
            )}
        >
            <UnstableComponent />
        </Catch>
    );
};

Передача дополнительных свойств в запасной компонент

tsx
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-узел вне корневого элемента компонента. Полезно для модальных окон, подсказок, выпадающих списков и уведомлений.

Сигнатура типа

typescript
const Portal: PC<{
    root: HTMLElement;
    children?: any | any[];
}>

Свойства

СвойствоТипОбязательноОписание
rootHTMLElementДаЦелевой DOM-элемент, в который должны быть рендерены дочерние элементы.
childrenany | any[]НетСодержимое для рендеринга внутри корневого элемента портала.

Базовое использование

tsx
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>
    );
};

Реактивное содержимое внутри портала

Дочерние элементы внутри портала полностью реактивны — сигналы обновляют их автоматически:

tsx
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 ничего не рендерит:

tsx
<Portal root={null} children={<span>Не появится</span>} />
// ничего не рендерит

Defer

Показывает начальное запасное значение, пока готовится фактическое значение (например, ожидание разрешения Promise или пока сигнал не получит не-promise значение).

Сигнатура типа

typescript
const Defer: PC<{
    initial?: any;
    value: Signal<any>;
}>

Свойства

СвойствоТипОбязательноОписание
initialanyНетЗапасное содержимое, рендерящееся пока value содержит Promise или ещё не готово.
valueSignal<any>ДаСигнал, значение которого отслеживается. Когда разрешённое значение сигнала не является Promise, оно заменяет содержимое initial.

Отложенный Promise

tsx
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}
        />
    );
};

Последовательность рендеринга:

  1. Немедленно показывает Загрузка...
  2. Через 2 секунды, когда Promise разрешается, показывает Загружено!

Отложенное обычное (не-promise) значение

Если сигнал уже содержит обычное значение (не Promise), Defer рендерит его немедленно — initial пропускается:

tsx
<Defer
    initial="Ожидание..."
    value={signal('Реальный контент')}
/>
// Немедленно рендерит "Реальный контент"

Реактивное переключение с Promise на обычное значение

Когда сигнал меняется с Promise на обычное значение, Defer переключается с заглушки на реальный контент:

tsx
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-узлов:

tsx
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 автоматически очищает своё внутреннее отслеживание сигналов.

Опубликовано под лицензией Apache License 2.0