Generic типы

Generic-типы в библиотеке Floating UI используются для строгой типизации взаимодействия между опорным (reference) и плавающим (floating) элементами, а также для расширяемых данных, передаваемых через middleware. Они позволяют сохранять гибкость API при высокой точности типизации.


Общая структура generic-типов

Ключевые generic-параметры применяются в следующих местах:

  • позиционирование (computePosition)
  • middleware
  • пользовательские платформы (platform adapters)
  • контекст взаимодействия (например, в React-обертках)

Базовая сигнатура функции позиционирования:

function computePosition<
  ReferenceElement extends Element | VirtualElement,
  FloatingElement extends HTMLElement
>(
  reference: ReferenceElement,
  floating: FloatingElement,
  options?: ComputePositionConfig
): Promise<ComputePositionReturn>;

Особенности:

  • ReferenceElement может быть как реальным DOM-элементом, так и виртуальным объектом (например, координаты курсора)
  • FloatingElement ограничен HTMLElement, так как он должен поддерживать стили и позиционирование

VirtualElement и расширение generic

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"
};

Generic в middleware

Middleware — ключевая часть Floating UI, и они активно используют generics для передачи пользовательских данных.

Базовый тип:

type Middleware<
  Options = any,
  Data = any
> = {
  name: string;
  options?: Options;
  fn: (state: MiddlewareState) => MiddlewareReturn<Data>;
};

Параметры:

  • Options — тип конфигурации middleware
  • Data — тип данных, возвращаемых middleware

Типизация MiddlewareReturn

type 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
      }
    };
  }
};

Aggregation данных middleware

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;

Generic в platform API

Платформа отвечает за абстракцию 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>;
}

Generic в React-обертках

В @floating-ui/react generics используются для строгой типизации ref-ов:

function useFloating<
  ReferenceType extends Element = HTMLElement,
  FloatingType extends HTMLElement = HTMLElement
>() {
  // ...
}

Пример:

const { refs } = useFloating<HTMLButtonElement, HTMLDivElement>();

Теперь:

  • refs.reference — строго HTMLButtonElement
  • refs.floating — строго HTMLDivElement

Расширение типов через utility-типы

Часто используются стандартные utility-типы TypeScript:

Partial

type PartialOptions<T> = Partial<T>;

Required

type FullOptions<T> = Required<T>;

Record

type MiddlewareMap = Record<string, unknown>;

Ограничения generic (extends)

Ограничение типов используется для обеспечения корректности:

function processElement<T extends Element>(el: T): T {
  return el;
}

В Floating UI:

ReferenceElement extends Element | VirtualElement

Это гарантирует, что объект либо DOM-элемент, либо совместим с VirtualElement.


Вывод типов (type inference)

TypeScript автоматически выводит generic-типы:

computePosition(button, tooltip);

Здесь:

  • ReferenceElementHTMLButtonElement
  • FloatingElementHTMLDivElement

Явное указание требуется только при сложных сценариях:

computePosition<VirtualElement, HTMLDivElement>(virtualRef, tooltip);

Conditional types в Floating UI

Используются для динамической типизации:

type ElementType<T> = T extends VirtualElement
  ? VirtualElement
  : Element;

Применение:

  • различение логики для виртуальных и реальных элементов
  • адаптация API

Типизация reset в middleware

type Reset = {
  placement?: Placement;
  rects?: boolean;
};

Можно расширить:

type CustomReset<T> = Reset & {
  custom?: T;
};

Практика: строго типизированный middleware

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

Композиция generic-типов

Можно комбинировать несколько middleware с разными типами:

type CombinedData = {
  offset?: OffsetData;
  size?: SizeData;
};

Ошибки типизации и их устранение

Проблема: any в middlewareData Решение: явное приведение типов

const data = middlewareData as CombinedData;

Расширение API через generics

Создание собственных оберток:

function createFloating<
  R extends Element | VirtualElement,
  F extends HTMLElement
>(reference: R, floating: F) {
  return computePosition(reference, floating);
}

Ключевые преимущества использования generic

  • строгая типизация без потери гибкости
  • возможность расширения middleware
  • безопасная работа с VirtualElement
  • улучшенный DX (подсказки, автодополнение)
  • масштабируемость архитектуры