Явная типизация конфигурационных объектов устраняет магические значения, упрощает рефакторинг и позволяет IDE показывать автодополнение при создании настроек.
import type { FormatOptions } from 'timeago.js';
type SupportedLocale = 'ru' | 'en_US' | 'de' | 'fr' | 'zh_CN';
interface TimeagoConfig {
locale: SupportedLocale;
relativeDate?: Date | number;
fallback?: string;
updateInterval?: number;
}
const defaultConfig: TimeagoConfig = {
locale: 'ru',
updateInterval: 60_000,
};
// Настройки форматирования
interface FormatConfig {
locale: SupportedLocale;
relativeDate?: Date | number;
}
// Настройки рендеринга
interface RenderConfig {
locale: SupportedLocale;
updateInterval: number;
batchSize: number;
}
// Настройки поведения при ошибках
interface ErrorConfig {
fallback: string;
onError?: (err: Error) => void;
throwErrors: boolean;
}
// Объединённая конфигурация
interface TimeagoConfig extends FormatConfig, RenderConfig, ErrorConfig {}
type ReadonlyConfig = Readonly<{
locale: SupportedLocale;
updateInterval: number;
batchSize: number;
}>;
const CONFIG: ReadonlyConfig = {
locale: 'ru',
updateInterval: 60_000,
batchSize: 50,
};
// CONFIG.locale = 'de'; // Ошибка: нельзя изменить readonly
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};
interface TimeagoConfig {
format: {
locale: SupportedLocale;
fallback: string;
};
render: {
interval: number;
batchSize: number;
};
}
const config: DeepReadonly<TimeagoConfig> = {
format: { locale: 'ru', fallback: 'давно' },
render: { interval: 60_000, batchSize: 50 },
};
// config.format.locale = 'de'; // Ошибка
import { format } from 'timeago.js';
interface TimeagoConfig {
locale: SupportedLocale;
updateInterval: number;
batchSize: number;
fallback: string;
}
const DEFAULT_CONFIG: TimeagoConfig = {
locale: 'ru',
updateInterval: 60_000,
batchSize: 50,
fallback: 'давно',
};
function createConfig(overrides?: Partial<TimeagoConfig>): TimeagoConfig {
return { ...DEFAULT_CONFIG, ...overrides };
}
// Использование
const config = createConfig({ locale: 'de', batchSize: 100 });
import { format, render, register } from 'timeago.js';
type SupportedLocale = 'ru' | 'en_US' | 'de';
class TimeagoBuilder {
private config: {
locale: SupportedLocale;
updateInterval: number;
batchSize: number;
} = {
locale: 'ru',
updateInterval: 60_000,
batchSize: 50,
};
setLocale(locale: SupportedLocale): this {
this.config.locale = locale;
return this;
}
setInterval(ms: number): this {
if (ms < 1000) throw new RangeError('Interval must be >= 1000ms');
this.config.updateInterval = ms;
return this;
}
setBatchSize(size: number): this {
if (size < 1) throw new RangeError('Batch size must be >= 1');
this.config.batchSize = size;
return this;
}
build(): Readonly<typeof this.config> {
return Object.freeze({ ...this.config });
}
}
const config = new TimeagoBuilder()
.setLocale('de')
.setInterval(30_000)
.setBatchSize(100)
.build();
interface TimeagoConfig {
locale: string;
updateInterval: number;
maxElements: number;
}
const VALID_LOCALES = new Set(['ru', 'en_US', 'de', 'fr', 'zh_CN']);
function validateConfig(config: unknown): TimeagoConfig {
if (typeof config !== 'object' || config === null) {
throw new TypeError('Config must be an object');
}
const c = config as Record<string, unknown>;
if (typeof c.locale !== 'string' || !VALID_LOCALES.has(c.locale)) {
throw new TypeError(`Invalid locale: ${c.locale}`);
}
if (typeof c.updateInterval !== 'number' || c.updateInterval < 1000) {
throw new RangeError('updateInterval must be a number >= 1000');
}
if (typeof c.maxElements !== 'number' || c.maxElements < 1) {
throw new RangeError('maxElements must be a number >= 1');
}
return c as TimeagoConfig;
}
type SupportedLocale = 'ru' | 'en_US' | 'de';
const LOCALE_VALUES: Record<string, SupportedLocale> = {
ru: 'ru',
en_US: 'en_US',
de: 'de',
};
function getLocaleFromEnv(): SupportedLocale {
const raw = process.env.TIMEAGO_LOCALE ?? '';
return LOCALE_VALUES[raw] ?? 'ru';
}
interface AppConfig {
timeago: {
locale: SupportedLocale;
interval: number;
};
}
const appConfig: AppConfig = {
timeago: {
locale: getLocaleFromEnv(),
interval: Number(process.env.TIMEAGO_INTERVAL) || 60_000,
},
};
type TimeagoMode = 'static' | 'live';
type StaticConfig = {
mode: 'static';
locale: SupportedLocale;
};
type LiveConfig = {
mode: 'live';
locale: SupportedLocale;
updateInterval: number;
maxElements?: number;
};
type TimeagoConfig = StaticConfig | LiveConfig;
function applyConfig(config: TimeagoConfig, el: Element): void {
if (config.mode === 'static') {
// Нет таймеров — только format
el.textContent = format(el.getAttribute('datetime') ?? '', config.locale);
} else {
// Живой режим
render(el as HTMLElement, config.locale);
}
}
| Паттерн | Применение |
|---|---|
Readonly<T> |
Иммутабельная конфигурация |
Partial<T> |
Переопределение части значений через override |
Required<T> |
Убрать все optional поля — все должны быть заданы |
Builder pattern |
Пошаговое конструирование с валидацией на каждом шаге |
Discriminated Union |
Разные конфиги для разных режимов работы |