Типы
Nullable
type Nullable<T> = T | null;
type Optional<T> = T | undefined;
type Maybe<T> = T | null | undefined;ValueOf
type ValueOf<T> = T[keyof T];
const USER_ROLES = {
admin: 'admin',
manager: 'manager',
viewer: 'viewer',
} as const;
type UserRole = ValueOf<typeof USER_ROLES>;StrictOmit и StrictPick
type StrictOmit<T, K extends keyof T> = Omit<T, K>;
type StrictPick<T, K extends keyof T> = Pick<T, K>;Обычный Omit<User, 'unknown'> не ругается на несуществующий ключ. StrictOmit заставляет TypeScript проверить ключ.
AsyncReturnType
type AsyncReturnType<T extends (...args: never[]) => Promise<unknown>> =
Awaited<ReturnType<T>>;async function getCurrentUser() {
return { id: '1', name: 'Ada' };
}
type CurrentUser = AsyncReturnType<typeof getCurrentUser>;ApiResult
type ApiSuccess<T> = {
ok: true;
data: T;
};
type ApiFailure = {
ok: false;
error: {
code: string;
message: string;
};
};
type ApiResult<T> = ApiSuccess<T> | ApiFailure;Guards
function isDefined<T>(value: T | null | undefined): value is T {
return value !== null && value !== undefined;
}const users = maybeUsers.filter(isDefined);function assertNever(value: never): never {
throw new Error(`Unexpected value: ${String(value)}`);
}function getRoleLabel(role: UserRole) {
switch (role) {
case 'admin':
return 'Администратор';
case 'manager':
return 'Менеджер';
case 'viewer':
return 'Наблюдатель';
default:
return assertNever(role);
}
}Форматирование
function formatCurrency(value: number, currency = 'RUB', locale = 'ru-RU') {
return new Intl.NumberFormat(locale, {
style: 'currency',
currency,
maximumFractionDigits: 0,
}).format(value);
}function formatPlural(
value: number,
forms: readonly [one: string, few: string, many: string],
) {
const mod10 = value % 10;
const mod100 = value % 100;
if (mod10 === 1 && mod100 !== 11) return forms[0];
if (mod10 >= 2 && mod10 <= 4 && (mod100 < 12 || mod100 > 14)) return forms[1];
return forms[2];
}formatPlural(3, ['задача', 'задачи', 'задач']);Работа с объектами
function pick<T extends object, K extends keyof T>(object: T, keys: readonly K[]) {
return keys.reduce(
(result, key) => {
result[key] = object[key];
return result;
},
{} as Pick<T, K>,
);
}function omit<T extends object, K extends keyof T>(object: T, keys: readonly K[]) {
const entries = Object.entries(object).filter(
([key]) => !keys.includes(key as K),
);
return Object.fromEntries(entries) as Omit<T, K>;
}Работа с URL
function getSearchParam<T extends string>(
params: URLSearchParams,
key: string,
fallback: T,
) {
return (params.get(key) as T | null) ?? fallback;
}function setSearchParam(params: URLSearchParams, key: string, value?: string) {
const nextParams = new URLSearchParams(params);
if (!value) {
nextParams.delete(key);
} else {
nextParams.set(key, value);
}
return nextParams;
}Async helpers
function sleep(ms: number) {
return new Promise((resolve) => window.setTimeout(resolve, ms));
}async function retry<T>(
task: () => Promise<T>,
options: { retries: number; delayMs: number },
) {
let lastError: unknown;
for (let attempt = 0; attempt <= options.retries; attempt += 1) {
try {
return await task();
} catch (error) {
lastError = error;
if (attempt < options.retries) {
await sleep(options.delayMs);
}
}
}
throw lastError;
}Storage
function readJsonStorage<T>(key: string, fallback: T): T {
const rawValue = localStorage.getItem(key);
if (!rawValue) return fallback;
try {
return JSON.parse(rawValue) as T;
} catch {
return fallback;
}
}function writeJsonStorage<T>(key: string, value: T) {
localStorage.setItem(key, JSON.stringify(value));
}