Типизация конфигурации в Tom Select строится вокруг описания объекта настроек, который передаётся при инициализации инстанса, а также вокруг расширяемых типов опций, элементов и событий. Основная цель типизации — зафиксировать контракт между пользовательским кодом и внутренним поведением компонента, минимизировать ошибки конфигурации и обеспечить автодополнение в редакторах с поддержкой TypeScript.
Конфигурация представляет собой объект, содержащий набор полей, определяющих поведение селектора: режим выбора, источники данных, рендеринг, фильтрацию, загрузку и обработку значений.
В типизированной среде этот объект описывается интерфейсом:
interface TomSelectSettings {
valueField?: string;
labelField?: string;
searchField?: string | string[];
disabledField?: string;
options?: unknown[];
items?: string[];
maxItems?: number;
maxOptions?: number;
create?: boolean | ((input: string) => boolean);
createOnBlur?: boolean;
persist?: boolean;
preload?: boolean | 'focus';
load?: (query: string, callback: (options: unknown[]) => void) => void;
render?: Record<string, (data: any, escape: (v: string) => string) => string>;
sortField?: string | ((a: any, b: any) => number);
placeholder?: string;
onInitialize?: () => void;
onChange?: (value: string) => void;
}
Даже в базовом виде видно, что ключевая сложность заключается в неоднородности значений: часть полей строго строковые, часть — функции, часть — массивы с неизвестной структурой.
Слабая типизация unknown[] быстро становится
ограничением. В реальных проектах структура опции почти всегда известна
заранее: идентификатор, метка, дополнительные метаданные.
Типизация улучшается через дженерики:
interface BaseOption {
id: string;
title: string;
}
interface TomSelectSettings<TOption = BaseOption> {
valueField: keyof TOption;
labelField: keyof TOption;
searchField?: keyof TOption | (keyof TOption)[];
options?: TOption[];
items?: (keyof TOption extends string ? string : never)[];
load?: (query: string, callback: (options: TOption[]) => void) => void;
render?: {
option?: (data: TOption, escape: (v: string) => string) => string;
item?: (data: TOption, escape: (v: string) => string) => string;
};
}
Использование дженерика позволяет связать конфигурацию с конкретной моделью данных:
type UserOption = {
id: string;
name: string;
role: string;
};
const settings: TomSelectSettings<UserOption> = {
valueField: 'id',
labelField: 'name',
searchField: ['name', 'role'],
};
Компилятор теперь контролирует соответствие ключей
valueField, labelField и
searchField реальной структуре объекта.
Конфигурация включает события жизненного цикла, которые часто используются для интеграции с внешними системами.
interface TomSelectEvents<TOption> {
onInitialize?: () => void;
onChange?: (value: string | string[]) => void;
onItemAdd?: (value: string, item: TOption) => void;
onItemRemove?: (value: string) => void;
onOptionAdd?: (option: TOption) => void;
}
Типизация событий особенно важна при работе с динамическими
источниками данных, где item должен строго соответствовать
типу опции. Это предотвращает рассинхронизацию между визуальным
состоянием и моделью данных.
Рендер-функции являются одним из наиболее гибких, но и наиболее опасных мест конфигурации. Они напрямую формируют HTML, что требует строгого контроля типов входных данных.
interface TomSelectRender<TOption> {
option?: (data: TOption, escape: (value: string) => string) => string;
item?: (data: TOption, escape: (value: string) => string) => string;
optgroup_header?: (data: any, escape: (value: string) => string) => string;
no_results?: (data: { input: string }, escape: (value: string) => string) => string;
}
Здесь важным аспектом является функция escape, которая
типизируется отдельно и используется для предотвращения XSS при
генерации HTML.
Одной из ключевых возможностей TypeScript является расширение существующих интерфейсов без изменения исходного кода библиотеки.
declare module 'tom-select' {
interface TomSelectSettings<TOption> {
apiEndpoint?: string;
debounceTime?: number;
}
}
Это позволяет адаптировать конфигурацию под специфические требования проекта, например добавлять параметры для интеграции с REST API или GraphQL без форка библиотеки.
Асинхронная загрузка — одна из центральных частей конфигурации. Ошибки здесь часто связаны с несоответствием структуры ответа сервера ожидаемому типу.
interface LoadCallback<TOption> {
(options: TOption[]): void;
}
interface TomSelectSettings<TOption> {
load?: (
query: string,
callback: LoadCallback<TOption>
) => void | Promise<void>;
}
При использовании Promise-ориентированного подхода добавляется дополнительный уровень контроля:
load?: (query: string) => Promise<TOption[]>;
Такой вариант упрощает интеграцию с современными HTTP-клиентами и исключает необходимость ручного вызова callback.
Конфигурация определяет не только поведение, но и форму состояния выбранных значений.
interface TomSelectState<TValue = string> {
items: TValue[];
options: Record<string, unknown>[];
activeItems: TValue[];
}
При строгой типизации можно связать значение с конкретным полем опции:
interface TomSelectSettings<TOption, TValue extends keyof TOption = keyof TOption> {
valueField: TValue;
items?: TOption[TValue][];
}
Это ограничивает возможность случайной передачи несовместимых значений и повышает предсказуемость работы компонента.
Группы (optgroups) требуют отдельного описания структуры, так как они отделены от обычных опций.
interface OptGroup {
value: string;
label: string;
options: unknown[];
}
interface TomSelectSettings<TOption> {
optgroups?: OptGroup[];
optgroupField?: keyof TOption;
}
При расширенной типизации можно связать группы с конкретными ключами данных, что позволяет статически проверять корректность структуры группировки.
Tom Select поддерживает плагины, которые также требуют типизации. Каждый плагин может расширять конфигурацию.
interface PluginSettings {
[pluginName: string]: unknown;
}
interface TomSelectSettings<TOption> {
plugins?: string[] | PluginSettings;
}
При строгой архитектуре плагины типизируются через модульное расширение:
interface DragDropPluginSettings {
enableSwap?: boolean;
}
declare module 'tom-select' {
interface TomSelectSettings<TOption> {
dragDrop?: DragDropPluginSettings;
}
}
При использовании as const и infer-типизации можно
автоматически извлекать типы из конфигурации:
const config = {
valueField: 'id',
labelField: 'name',
maxItems: 5
} as const;
type ConfigType = typeof config;
Это позволяет строить конфигурационные фабрики, где типы выводятся из литералов, снижая дублирование описаний.
В зрелой типизации конфигурация превращается в композицию нескольких слоёв:
Такой подход обеспечивает устойчивость конфигурационного слоя и делает интеграцию с Tom Select предсказуемой в масштабируемых приложениях, где селекторы используются в динамических формах, административных панелях и сложных UI-композициях.