Generic-типы в библиотеке Floating UI используются для строгой типизации взаимодействия между опорным (reference) и плавающим (floating) элементами, а также для расширяемых данных, передаваемых через middleware. Они позволяют сохранять гибкость API при высокой точности типизации.
Ключевые generic-параметры применяются в следующих местах:
computePosition)Базовая сигнатура функции позиционирования:
function computePosition<
ReferenceElement extends Element | VirtualElement,
FloatingElement extends HTMLElement
>(
reference: ReferenceElement,
floating: FloatingElement,
options?: ComputePositionConfig
): Promise<ComputePositionReturn>;
Особенности:
ReferenceElement может быть как реальным DOM-элементом,
так и виртуальным объектом (например, координаты курсора)FloatingElement ограничен HTMLElement, так
как он должен поддерживать стили и позиционированиеFloating UI позволяет использовать виртуальные элементы через интерфейс:
interface VirtualElement {
getBoundingClientRect(): DOMRect;
contextElement?: Element;
}
Generic-типы позволяют подставлять собственные реализации:
type CustomVirtualElement = VirtualElement & {
customData: string;
};
Использование:
const virtualRef: CustomVirtualElement = {
getBoundingClientRect: () => new DOMRect(0, 0, 100, 100),
customData: "example"
};
Middleware — ключевая часть Floating UI, и они активно используют generics для передачи пользовательских данных.
Базовый тип:
type Middleware<
Options = any,
Data = any
> = {
name: string;
options?: Options;
fn: (state: MiddlewareState) => MiddlewareReturn<Data>;
};
Options — тип конфигурации middlewareData — тип данных, возвращаемых middlewaretype MiddlewareReturn<Data = any> = {
x?: number;
y?: number;
data?: Data;
reset?: Reset;
};
Generic Data позволяет точно описывать, какие данные
middleware добавляет в общий контекст.
Пример:
type OffsetData = {
offsetX: number;
offsetY: number;
};
const offsetMiddleware: Middleware<{ offset: number }, OffsetData> = {
name: 'offset',
options: { offset: 10 },
fn({ x, y }) {
return {
x: x + 10,
y: y + 10,
data: {
offsetX: 10,
offsetY: 10
}
};
}
};
Floating UI агрегирует данные всех middleware в объект
middlewareData.
Тип:
type MiddlewareData = {
[key: string]: any;
};
С generics можно сузить тип:
type CustomMiddlewareData = {
offset?: OffsetData;
arrow?: { x: number; y: number };
};
Использование:
const { middlewareData } = await computePosition(...);
const offset = (middlewareData as CustomMiddlewareData).offset;
Платформа отвечает за абстракцию DOM-операций. Generic-типы позволяют адаптировать Floating UI под нестандартные среды (например, canvas или React Native).
Базовый интерфейс:
interface Platform {
getElementRects: (args: {
reference: Element | VirtualElement;
floating: HTMLElement;
}) => Promise<Rects>;
}
Можно создать расширенную платформу:
interface CustomPlatform<
Reference = Element,
Floating = HTMLElement
> extends Platform {
getElementRects: (args: {
reference: Reference;
floating: Floating;
}) => Promise<Rects>;
}
В @floating-ui/react generics используются для строгой
типизации ref-ов:
function useFloating<
ReferenceType extends Element = HTMLElement,
FloatingType extends HTMLElement = HTMLElement
>() {
// ...
}
Пример:
const { refs } = useFloating<HTMLButtonElement, HTMLDivElement>();
Теперь:
refs.reference — строго
HTMLButtonElementrefs.floating — строго HTMLDivElementЧасто используются стандартные utility-типы TypeScript:
type PartialOptions<T> = Partial<T>;
type FullOptions<T> = Required<T>;
type MiddlewareMap = Record<string, unknown>;
Ограничение типов используется для обеспечения корректности:
function processElement<T extends Element>(el: T): T {
return el;
}
В Floating UI:
ReferenceElement extends Element | VirtualElement
Это гарантирует, что объект либо DOM-элемент, либо совместим с VirtualElement.
TypeScript автоматически выводит generic-типы:
computePosition(button, tooltip);
Здесь:
ReferenceElement → HTMLButtonElementFloatingElement → HTMLDivElementЯвное указание требуется только при сложных сценариях:
computePosition<VirtualElement, HTMLDivElement>(virtualRef, tooltip);
Используются для динамической типизации:
type ElementType<T> = T extends VirtualElement
? VirtualElement
: Element;
Применение:
type Reset = {
placement?: Placement;
rects?: boolean;
};
Можно расширить:
type CustomReset<T> = Reset & {
custom?: T;
};
type SizeOptions = {
padding: number;
};
type SizeData = {
width: number;
height: number;
};
const sizeMiddleware: Middleware<SizeOptions, SizeData> = {
name: 'size',
options: { padding: 8 },
fn({ rects }) {
return {
data: {
width: rects.floating.width,
height: rects.floating.height
}
};
}
};
Можно комбинировать несколько middleware с разными типами:
type CombinedData = {
offset?: OffsetData;
size?: SizeData;
};
Проблема: any в middlewareData
Решение: явное приведение типов
const data = middlewareData as CombinedData;
Создание собственных оберток:
function createFloating<
R extends Element | VirtualElement,
F extends HTMLElement
>(reference: R, floating: F) {
return computePosition(reference, floating);
}