JavaScript/TypeScript Сниппеты
Полезные сниппеты для разработки на JavaScript и TypeScript.
Утилиты и помощники
Работа с массивами
// Удаление дубликатов
const removeDuplicates = (arr) => [...new Set(arr)];
// Группировка элементов по ключу
const groupBy = (arr, key) => {
return arr.reduce((groups, item) => {
const value = item[key];
groups[value] = groups[value] || [];
groups[value].push(item);
return groups;
}, {});
};
// Чанки массива
const chunk = (arr, size) => {
const chunks = [];
for (let i = 0; i < arr.length; i += size) {
chunks.push(arr.slice(i, i + size));
}
return chunks;
};
// Перемешивание массива (алгоритм Фишера-Йейтса)
const shuffle = (arr) => {
const shuffled = [...arr];
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
}
return shuffled;
};
// Пересечение массивов
const intersection = (arr1, arr2) => {
return arr1.filter(value => arr2.includes(value));
};
// Разность массивов
const difference = (arr1, arr2) => {
return arr1.filter(value => !arr2.includes(value));
};
// Плоский массив из вложенных
const flatten = (arr) => {
return arr.reduce((flat, item) => {
return flat.concat(Array.isArray(item) ? flatten(item) : item);
}, []);
};
Работа с объектами
// Глубокое копирование
const deepClone = (obj) => {
if (obj === null || typeof obj !== 'object') return obj;
if (obj instanceof Date) return new Date(obj.getTime());
if (obj instanceof Array) return obj.map(item => deepClone(item));
const cloned = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = deepClone(obj[key]);
}
}
return cloned;
};
// Глубокое слияние объектов
const deepMerge = (target, source) => {
const result = { ...target };
for (let key in source) {
if (source.hasOwnProperty(key)) {
if (typeof source[key] === 'object' && source[key] !== null && !Array.isArray(source[key])) {
result[key] = deepMerge(target[key] || {}, source[key]);
} else {
result[key] = source[key];
}
}
}
return result;
};
// Получение значения по пути
const getByPath = (obj, path, defaultValue = undefined) => {
const keys = path.split('.');
let result = obj;
for (let key of keys) {
if (result?.[key] !== undefined) {
result = result[key];
} else {
return defaultValue;
}
}
return result;
};
// Установка значения по пути
const setByPath = (obj, path, value) => {
const keys = path.split('.');
let current = obj;
for (let i = 0; i < keys.length - 1; i++) {
const key = keys[i];
if (!(key in current) || typeof current[key] !== 'object') {
current[key] = {};
}
current = current[key];
}
current[keys[keys.length - 1]] = value;
};
// Фильтрация объекта по ключам
const pick = (obj, keys) => {
return keys.reduce((result, key) => {
if (key in obj) {
result[key] = obj[key];
}
return result;
}, {});
};
// Исключение ключей из объекта
const omit = (obj, keys) => {
const result = { ...obj };
keys.forEach(key => delete result[key]);
return result;
};
Функциональное программирование
// Compose функций
const compose = (...fns) => (value) => fns.reduceRight((acc, fn) => fn(acc), value);
// Pipe функций
const pipe = (...fns) => (value) => fns.reduce((acc, fn) => fn(acc), value);
// Каррирование
const curry = (fn) => {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
return function(...nextArgs) {
return curried.apply(this, args.concat(nextArgs));
};
}
};
};
// Мемоизация
const memoize = (fn) => {
const cache = new Map();
return (...args) => {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn(...args);
cache.set(key, result);
return result;
};
};
// Throttle
const throttle = (func, delay) => {
let timeoutId;
let lastExecTime = 0;
return function(...args) {
const currentTime = Date.now();
if (currentTime - lastExecTime > delay) {
func.apply(this, args);
lastExecTime = currentTime;
} else {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, args);
lastExecTime = Date.now();
}, delay - (currentTime - lastExecTime));
}
};
};
// Debounce
const debounce = (func, delay) => {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
};
Асинхронное программирование
Promise утилиты
// Timeout для Promise
const withTimeout = (promise, ms) => {
const timeout = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Timeout')), ms);
});
return Promise.race([promise, timeout]);
};
// Retry для Promise
const retry = async (fn, maxAttempts = 3, delay = 1000) => {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await fn();
} catch (error) {
if (attempt === maxAttempts) {
throw error;
}
await new Promise(resolve => setTimeout(resolve, delay));
}
}
};
// Параллельное выполнение с ограничением
const promiseLimit = async (promises, limit) => {
const results = [];
const executing = [];
for (const promise of promises) {
const p = Promise.resolve(promise).then(result => {
executing.splice(executing.indexOf(p), 1);
return result;
});
results.push(p);
if (promises.length >= limit) {
executing.push(p);
if (executing.length >= limit) {
await Promise.race(executing);
}
}
}
return Promise.all(results);
};
// Promise.allSettled полифилл
const allSettled = (promises) => {
return Promise.all(
promises.map(promise =>
Promise.resolve(promise)
.then(value => ({ status: 'fulfilled', value }))
.catch(reason => ({ status: 'rejected', reason }))
)
);
};
// Последовательное выполнение
const sequence = async (promises) => {
const results = [];
for (const promise of promises) {
results.push(await promise);
}
return results;
};
Async/Await утилиты
// Обработчик ошибок для async/await
const to = (promise) => {
return promise
.then(data => [null, data])
.catch(err => [err, null]);
};
// Использование:
// const [error, data] = await to(fetchData());
// Sleep функция
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
// Async forEach
const asyncForEach = async (array, callback) => {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array);
}
};
// Async map
const asyncMap = async (array, callback) => {
const results = [];
for (let index = 0; index < array.length; index++) {
results.push(await callback(array[index], index, array));
}
return results;
};
// Async filter
const asyncFilter = async (array, callback) => {
const results = [];
for (let index = 0; index < array.length; index++) {
if (await callback(array[index], index, array)) {
results.push(array[index]);
}
}
return results;
};
HTTP клиенты
Fetch обертка
class APIClient {
constructor(baseURL, defaultHeaders = {}) {
this.baseURL = baseURL.replace(/\/$/, '');
this.defaultHeaders = {
'Content-Type': 'application/json',
...defaultHeaders
};
}
async request(endpoint, options = {}) {
const url = `${this.baseURL}/${endpoint.replace(/^\//, '')}`;
const config = {
headers: { ...this.defaultHeaders, ...options.headers },
...options
};
if (config.body && typeof config.body === 'object') {
config.body = JSON.stringify(config.body);
}
try {
const response = await fetch(url, config);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
return await response.json();
}
return await response.text();
} catch (error) {
console.error('API request failed:', error);
throw error;
}
}
get(endpoint, params = {}) {
const searchParams = new URLSearchParams(params);
const url = searchParams.toString() ? `${endpoint}?${searchParams}` : endpoint;
return this.request(url, { method: 'GET' });
}
post(endpoint, data) {
return this.request(endpoint, {
method: 'POST',
body: data
});
}
put(endpoint, data) {
return this.request(endpoint, {
method: 'PUT',
body: data
});
}
delete(endpoint) {
return this.request(endpoint, { method: 'DELETE' });
}
// Interceptors
setAuthToken(token) {
this.defaultHeaders['Authorization'] = `Bearer ${token}`;
}
removeAuthToken() {
delete this.defaultHeaders['Authorization'];
}
}
// Использование
const api = new APIClient('https://api.example.com');
api.setAuthToken('your-token');
const users = await api.get('users', { page: 1, limit: 10 });
const newUser = await api.post('users', { name: 'John', email: 'john@example.com' });
Axios конфигурация
import axios from 'axios';
// Создание экземпляра
const apiClient = axios.create({
baseURL: process.env.REACT_APP_API_URL || 'http://localhost:3000/api',
timeout: 10000,
headers: {
'Content-Type': 'application/json',
}
});
// Interceptor для запросов
apiClient.interceptors.request.use(
(config) => {
// Добавляем токен авторизации
const token = localStorage.getItem('auth_token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
// Логируем запросы в development
if (process.env.NODE_ENV === 'development') {
console.log('Request:', config);
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// Interceptor для ответов
apiClient.interceptors.response.use(
(response) => {
// Логируем успешные ответы
if (process.env.NODE_ENV === 'development') {
console.log('Response:', response);
}
return response.data;
},
(error) => {
// Обработка ошибок
if (error.response?.status === 401) {
// Перенаправляем на страницу входа
localStorage.removeItem('auth_token');
window.location.href = '/login';
}
if (error.response?.status === 403) {
console.error('Access denied');
}
if (error.response?.status >= 500) {
console.error('Server error');
}
return Promise.reject(error);
}
);
export default apiClient;
TypeScript утилиты
Типы и интерфейсы
// Utility types
type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
type RequiredKeys<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? never : K }[keyof T];
type OptionalKeys<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? K : never }[keyof T];
// API Response типы
interface APIResponse<T> {
data: T;
message: string;
success: boolean;
}
interface PaginatedResponse<T> extends APIResponse<T[]> {
pagination: {
page: number;
limit: number;
total: number;
totalPages: number;
};
}
// Ошибки
interface APIError {
code: string;
message: string;
details?: Record<string, any>;
}
// Конфигурация
interface AppConfig {
apiUrl: string;
timeout: number;
retries: number;
enableLogging: boolean;
}
// Event types
type EventHandler<T = any> = (event: T) => void;
type AsyncEventHandler<T = any> = (event: T) => Promise<void>;
// Функциональные типы
type Predicate<T> = (value: T) => boolean;
type Mapper<T, U> = (value: T) => U;
type Reducer<T, U> = (accumulator: U, currentValue: T) => U;
// Branded types
type Brand<T, U> = T & { __brand: U };
type UserId = Brand<string, 'UserId'>;
type Email = Brand<string, 'Email'>;
// Deep readonly
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};
Type Guards
// Примитивные типы
const isString = (value: any): value is string => typeof value === 'string';
const isNumber = (value: any): value is number => typeof value === 'number';
const isBoolean = (value: any): value is boolean => typeof value === 'boolean';
const isArray = (value: any): value is any[] => Array.isArray(value);
const isObject = (value: any): value is object =>
value !== null && typeof value === 'object' && !Array.isArray(value);
// Сложные типы
const isEmail = (value: string): value is Email => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(value);
};
const isValidDate = (value: any): value is Date => {
return value instanceof Date && !isNaN(value.getTime());
};
// Generic type guard
const hasProperty = <T extends object, K extends PropertyKey>(
obj: T,
key: K
): obj is T & Record<K, unknown> => {
return key in obj;
};
// Union type guards
type User = { type: 'user'; name: string; email: string };
type Admin = { type: 'admin'; name: string; permissions: string[] };
type Person = User | Admin;
const isUser = (person: Person): person is User => person.type === 'user';
const isAdmin = (person: Person): person is Admin => person.type === 'admin';
// API response type guard
const isAPIError = (response: any): response is APIError => {
return typeof response === 'object' &&
'code' in response &&
'message' in response;
};
Декораторы
// Method decorator для логирования
function Log(target: any, propertyName: string, descriptor: PropertyDescriptor) {
const method = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Calling ${propertyName} with arguments:`, args);
const result = method.apply(this, args);
console.log(`Method ${propertyName} returned:`, result);
return result;
};
}
// Class decorator для автобайндинга
function AutoBind<T extends { new (...args: any[]): {} }>(constructor: T) {
return class extends constructor {
constructor(...args: any[]) {
super(...args);
// Автобайндинг всех методов
Object.getOwnPropertyNames(Object.getPrototypeOf(this))
.filter(name => name !== 'constructor')
.filter(name => typeof (this as any)[name] === 'function')
.forEach(name => {
(this as any)[name] = (this as any)[name].bind(this);
});
}
};
}
// Property decorator для валидации
function Validate(validator: (value: any) => boolean, message: string) {
return function (target: any, propertyName: string) {
let value: any;
const getter = () => value;
const setter = (newValue: any) => {
if (!validator(newValue)) {
throw new Error(`${propertyName}: ${message}`);
}
value = newValue;
};
Object.defineProperty(target, propertyName, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
};
}
// Использование декораторов
@AutoBind
class UserService {
@Validate(isEmail, 'Must be a valid email')
email!: string;
@Log
async createUser(name: string, email: string) {
// Логика создания пользователя
return { id: '123', name, email };
}
}
Менеджеры состояния
Простой Store
type Listener<T> = (state: T) => void;
class Store<T> {
private state: T;
private listeners: Set<Listener<T>> = new Set();
constructor(initialState: T) {
this.state = initialState;
}
getState(): T {
return this.state;
}
setState(newState: T | ((prevState: T) => T)): void {
this.state = typeof newState === 'function'
? (newState as (prevState: T) => T)(this.state)
: newState;
this.listeners.forEach(listener => listener(this.state));
}
subscribe(listener: Listener<T>): () => void {
this.listeners.add(listener);
return () => {
this.listeners.delete(listener);
};
}
// Computed values
select<U>(selector: (state: T) => U): U {
return selector(this.state);
}
}
// Использование
interface AppState {
user: { name: string; email: string } | null;
loading: boolean;
error: string | null;
}
const store = new Store<AppState>({
user: null,
loading: false,
error: null
});
// Подписка на изменения
const unsubscribe = store.subscribe((state) => {
console.log('State changed:', state);
});
// Обновление состояния
store.setState(prevState => ({
...prevState,
loading: true
}));