Переиспользование конфигураций

В проектах, где Slim Sel ect используется на десятках или сотнях элементов формы, повторяющиеся конфигурации быстро превращаются в источник дублирования и рассинхронизации поведения. Переиспользование конфигураций позволяет унифицировать и централизовать логику инициализации, снижая количество точечных ошибок и упрощая сопровождение.

Ключевая идея заключается в разделении конфигурации на базовые слои: общий набор параметров, доменные пресеты и локальные переопределения для конкретных инстансов.


Базовый конфигурационный объект

Наиболее простой способ переиспользования — выделение общего объекта настроек, который используется как основа для всех экземпляров Slim Select.

const baseSelectConfig = {
  placeholder: 'Выберите значение',
  allowDeselect: true,
  searchText: 'Поиск...',
  searchPlaceholder: 'Введите текст',
  searchHighlight: true,
};

При инициализации каждого нового селекта этот объект используется как шаблон:

new SlimSelect({
  select: '#city-select',
  ...baseSelectConfig,
});

Такой подход эффективен только на начальном этапе, поскольку не учитывает необходимость изоляции и динамического расширения конфигураций.


Проблема мутации и поверхностного копирования

Простое распространение через spread-оператор создаёт поверхностную копию. Это становится проблемой при наличии вложенных объектов конфигурации, например при использовании ajax, data или кастомных render функций.

const config = {
  ajax: {
    url: '/api/cities',
    delay: 300,
  },
};

При повторном использовании без глубокой копии возможны ситуации, когда один экземпляр изменяет общий объект.

Решение заключается в использовании фабрик конфигураций.


Фабрика конфигураций

Функция-фабрика позволяет генерировать новый объект конфигурации для каждого экземпляра, гарантируя изоляцию состояния.

function createBaseConfig() {
  return {
    placeholder: 'Выберите значение',
    allowDeselect: true,
    searchText: 'Поиск...',
    searchPlaceholder: 'Введите текст',
    searchHighlight: true,
    ajax: {
      delay: 250,
    },
  };
}

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

new SlimSelect({
  select: '#country-select',
  ...createBaseConfig(),
});

Фабрика становится особенно полезной при наличии параметров окружения или зависимости от контекста.


Параметризованные фабрики

Следующий уровень абстракции — фабрики, принимающие аргументы и адаптирующие конфигурацию под конкретный сценарий.

function createSelectConfig(type) {
  const base = {
    allowDeselect: true,
    searchHighlight: true,
  };

  if (type === 'city') {
    return {
      ...base,
      placeholder: 'Город',
      searchPlaceholder: 'Найти город',
      ajax: {
        url: '/api/cities',
        delay: 200,
      },
    };
  }

  if (type === 'country') {
    return {
      ...base,
      placeholder: 'Страна',
      searchPlaceholder: 'Найти страну',
      ajax: {
        url: '/api/countries',
        delay: 300,
      },
    };
  }

  return base;
}

Такой подход вводит концепцию конфигурационных профилей, где каждый тип компонента описывается декларативно.


Переиспользование через пресеты

При росте приложения целесообразно выделить фиксированные пресеты конфигураций, которые выступают как единицы повторного использования.

const SelectPresets = {
  default: {
    allowDeselect: true,
    searchHighlight: true,
    placeholder: 'Выберите...',
  },

  searchOnly: {
    searchHighlight: true,
    allowDeselect: false,
  },

  ajaxFast: {
    search: true,
    ajax: {
      delay: 150,
    },
  },
};

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

new SlimSelect({
  select: '#product-select',
  ...SelectPresets.ajaxFast,
  ajax: {
    url: '/api/products',
    ...SelectPresets.ajaxFast.ajax,
  },
});

Ключевой момент — аккуратное объединение вложенных структур, чтобы не терять параметры при расширении.


Глубокое объединение конфигураций

При наличии сложных пресетов требуется глубокое слияние объектов. Использование простого spread-оператора становится недостаточным.

function deepMerge(target, source) {
  const output = { ...target };

  for (const key in source) {
    if (
      source[key] &&
      typeof source[key] === 'object' &&
      !Array.isArray(source[key])
    ) {
      output[key] = deepMerge(output[key] || {}, source[key]);
    } else {
      output[key] = source[key];
    }
  }

  return output;
}

Применение:

const config = deepMerge(SelectPresets.default, {
  ajax: {
    url: '/api/users',
  },
});

Это позволяет безопасно комбинировать базовые конфигурации и локальные модификации без потери вложенных параметров.


Комбинирование пресетов

При росте системы часто возникает необходимость комбинировать несколько пресетов одновременно. Например, базовое поведение + AJAX + кастомный рендер.

const SelectMixins = {
  ajax: {
    search: true,
    ajax: {
      delay: 250,
    },
  },

  compact: {
    showSearch: false,
    allowDeselect: false,
  },
};

Композиция:

const config = deepMerge(
  SelectPresets.default,
  SelectMixins.ajax,
  SelectMixins.compact,
  {
    ajax: {
      url: '/api/data',
    },
  }
);

Такой подход формирует систему конфигураций, напоминающую CSS-модификаторы по принципу каскада.


Конфигурации на уровне атрибутов DOM

Ещё один способ переиспользования — хранение конфигураций прямо в DOM через data-атрибуты.

<select id="city-select"
        data-placeholder="Город"
        data-ajax-url="/api/cities"
        data-ajax-delay="200">
</select>

Парсинг:

function configFromDataset(element) {
  return {
    placeholder: element.dataset.placeholder,
    ajax: {
      url: element.dataset.ajaxUrl,
      delay: Number(element.dataset.ajaxDelay),
    },
  };
}
new SlimSelect({
  select: '#city-select',
  ...configFromDataset(document.querySelector('#city-select')),
});

Этот подход переносит часть конфигурации в разметку, облегчая повторное использование компонентов без изменения JavaScript-кода.


Глобальные конфигурации по умолчанию

В системах с единым стилем интерфейса часто вводится глобальный слой конфигурации, применяемый ко всем экземплярам Slim Select.

const GlobalSelectDefaults = {
  searchHighlight: true,
  allowDeselect: true,
  placeholder: 'Выберите значение',
};

Далее каждая локальная конфигурация наследует глобальные значения:

const config = deepMerge(GlobalSelectDefaults, {
  placeholder: 'Страна',
});

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


Переиспользование через модули конфигурации

При масштабировании приложения конфигурации целесообразно выносить в отдельные модули, разделённые по доменам.

/config/select/
  base.js
  user.js
  location.js
  product.js

Пример модуля:

export const userSelectConfig = {
  placeholder: 'Пользователь',
  ajax: {
    url: '/api/users',
    delay: 250,
  },
};

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

import { userSelectConfig } fr om './config/select/user.js';

new SlimSelect({
  select: '#user-select',
  ...userSelectConfig,
});

Такое разбиение делает конфигурации частью архитектуры приложения, а не локальной логикой UI.


Композиция конфигураций через функции высшего порядка

Более гибкий способ — использование функций, возвращающих модифицированные конфигурации.

const withAjax = (config, url) => ({
  ...config,
  ajax: {
    ...(config.ajax || {}),
    url,
  },
});
const withPlaceholder = (config, text) => ({
  ...config,
  placeholder: text,
});

Композиция:

const config = withPlaceholder(
  withAjax(SelectPresets.default, '/api/tags'),
  'Теги'
);

Этот подход формирует декларативную цепочку преобразований, где конфигурация становится неизменяемым объектом, проходящим через набор трансформеров.


Кэширование конфигураций

При частой инициализации одинаковых типов Slim Select имеет смысл кэшировать уже собранные конфигурации.

const configCache = new Map();

function getSelectConfig(type) {
  if (configCache.has(type)) {
    return configCache.get(type);
  }

  const config = createSelectConfig(type);
  configCache.set(type, config);

  return config;
}

Это уменьшает накладные расходы на сборку конфигураций при массовом рендеринге форм.


Конфигурации как часть архитектуры состояния

В зрелых системах конфигурации Slim Select перестают быть локальной деталью и становятся частью архитектуры состояния интерфейса. Они управляются так же, как данные приложения: через модули, композицию и изоляцию побочных эффектов.

Переиспользование конфигураций в таком подходе превращается из техники устранения дублирования в механизм стандартизации поведения UI-компонентов.