← Назад к вопросам
Как пошарить код между двумя участками кода без window?
2.2 Middle🔥 192 комментариев
#JavaScript Core
Комментарии (2)
🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Способы шаринга кода между модулями без использования window
Использование глобального объекта window - это плохая практика. Есть множество более чистых способов шаринга функций и данных между модулями.
1. ES6 модули (экспорт/импорт)
Это наиболее правильный и современный способ:
// utils/math.js
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}
export const PI = 3.14159;
// Другой файл
import { add, multiply, PI } from './utils/math.js';
console.log(add(2, 3)); // 5
console.log(multiply(4, 5)); // 20
console.log(PI); // 3.14159
Преимущества:
- Явная зависимость (видно, откуда приходит функция)
- Static analysis (линтеры и бандлеры могут оптимизировать)
- Tree shaking (неиспользуемый код удаляется)
- IDE автодополнение работает идеально
2. Именованные экспорты vs Default экспорт
// Правильный паттерн: несколько функций
// utils/api.js
export async function fetchUser(id) { }
export async function fetchPosts(userId) { }
export async function saveUser(user) { }
// Использование
import { fetchUser, fetchPosts } from './utils/api.js';
// ПЛОХО: default экспорт для функций
// utils/api.js
export default async function fetchUser(id) { }
// Это скрывает название и затрудняет поиск
import someFunction from './utils/api.js'; // Что это функция?
3. Шаринг состояния через Context API
Для React компонентов, которые нужно шарить состояние:
// contexts/UserContext.js
import { createContext, useState } from 'react';
export const UserContext = createContext();
export function UserProvider({ children }) {
const [user, setUser] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const login = async (email, password) => {
setIsLoading(true);
try {
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify({ email, password })
});
const data = await response.json();
setUser(data);
return data;
} finally {
setIsLoading(false);
}
};
const logout = () => setUser(null);
return (
<UserContext.Provider value={{ user, isLoading, login, logout }}>
{children}
</UserContext.Provider>
);
}
// Использование в компонентах
import { useContext } from 'react';
import { UserContext } from './contexts/UserContext';
function Profile() {
const { user, logout } = useContext(UserContext);
return (
<div>
<p>User: {user?.name}</p>
<button onClick={logout}>Logout</button>
</div>
);
}
4. Custom hooks для шаринга логики
Если нужно шарить логику между компонентами:
// hooks/useApi.js
import { useState, useCallback } from 'react';
export function useApi(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const fetch = useCallback(async () => {
setLoading(true);
setError(null);
try {
const response = await fetch(url);
if (!response.ok) throw new Error('API Error');
const result = await response.json();
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}, [url]);
return { data, loading, error, fetch };
}
// Использование
function UsersList() {
const { data: users, loading, fetch } = useApi('/api/users');
useEffect(() => {
fetch();
}, [fetch]);
if (loading) return <p>Loading...</p>;
return users.map(user => <div key={user.id}>{user.name}</div>);
}
5. Singleton паттерн для инициализации один раз
Если нужна одна инстанция класса/сервиса:
// services/AuthService.js
class AuthService {
constructor() {
this.token = null;
this.user = null;
}
async login(credentials) {
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify(credentials)
});
const data = await response.json();
this.token = data.token;
this.user = data.user;
localStorage.setItem('token', this.token);
return data;
}
logout() {
this.token = null;
this.user = null;
localStorage.removeItem('token');
}
getToken() {
return this.token || localStorage.getItem('token');
}
}
// Singleton паттерн
let instance = null;
export function getAuthService() {
if (!instance) {
instance = new AuthService();
}
return instance;
}
// Использование
import { getAuthService } from './services/AuthService';
const auth = getAuthService();
await auth.login({ email: 'user@example.com', password: 'pass' });
// В другом месте кода
const auth2 = getAuthService();
console.log(auth === auth2); // true - одна инстанция
console.log(auth2.getToken()); // уже залогинен
6. Класс-утилиты для константных методов
// utils/formatters.js
export class DateFormatter {
static toLocaleDateString(date) {
return new Date(date).toLocaleDateString('ru-RU');
}
static toLocaleTimeString(date) {
return new Date(date).toLocaleTimeString('ru-RU');
}
static formatDuration(ms) {
const seconds = Math.floor(ms / 1000);
const minutes = Math.floor(seconds / 60);
return `${minutes}m ${seconds % 60}s`;
}
}
export class StringUtils {
static capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
static truncate(str, length) {
return str.length > length ? str.slice(0, length) + '...' : str;
}
}
// Использование
import { DateFormatter, StringUtils } from './utils/formatters';
DateFormatter.toLocaleDateString(new Date()); // "02.04.2026"
StringUtils.capitalize('hello'); // "Hello"
7. Шаринг конфигурации и констант
// config/index.js
export const API_BASE_URL = process.env.REACT_APP_API_URL || 'http://localhost:3000';
export const API_TIMEOUT = 30000;
export const RETRY_ATTEMPTS = 3;
export const ROUTES = {
HOME: '/',
PROFILE: '/profile',
SETTINGS: '/settings'
};
export const THEMES = {
LIGHT: 'light',
DARK: 'dark'
};
// Использование везде
import { API_BASE_URL, ROUTES } from './config';
const url = `${API_BASE_URL}/api/users`;
navigation.push(ROUTES.PROFILE);
8. Event Emitter для асинхронного шаринга событий
// services/EventEmitter.js
class EventEmitter {
constructor() {
this.events = {};
}
on(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
// unsubscribe функция
return () => {
this.events[event] = this.events[event].filter(cb => cb !== callback);
};
}
emit(event, data) {
if (this.events[event]) {
this.events[event].forEach(callback => callback(data));
}
}
}
export const eventBus = new EventEmitter();
// Использование в разных файлах
// file1.js
import { eventBus } from './services/EventEmitter';
eventBus.emit('user:login', { id: 123, name: 'John' });
// file2.js
import { eventBus } from './services/EventEmitter';
eventBus.on('user:login', (user) => {
console.log('User logged in:', user);
});
Сравнение подходов
| Способ | Использование | Плюсы | Минусы |
|---|---|---|---|
| ES6 модули | Функции, классы, константы | Стандартно, явно, безопасно | Требует бандлер |
| Context API | React состояние | Встроено в React | Может вызвать лишние ре-ренды |
| Custom hooks | React логика | Переиспользуемо, чистоты | Только для React |
| Singleton | Сервисы, утилиты | Одна инстанция | Может быть сложно тестировать |
| Event Emitter | Асинхронные события | Слабая связь | Сложнее отследить |
Правило: избегай window
// ПЛОХО
window.sharedData = { count: 0 };
window.increment = () => window.sharedData.count++;
// ХОРОШО
export const sharedData = { count: 0 };
export const increment = () => sharedData.count++;
// ЕЩЕ ЛУЧШЕ (функциональный подход)
export function createCounter() {
let count = 0;
return {
increment: () => ++count,
decrement: () => --count,
get: () => count
};
}
Этот подход гарантирует:
- Инкапсуляцию данных
- Отсутствие глобального загрязнения
- Простоту тестирования
- Явную зависимость между модулями
- Возможность tree shaking в бандлере