← Назад к вопросам

Как взаимодействовать с модулем не находясь внутри него?

2.0 Middle🔥 161 комментариев
#JavaScript Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как взаимодействовать с модулем не находясь внутри него

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

1. Экспорт/Импорт модулей (ES6)

Основной механизм:

// math.js - модуль
export function add(a, b) {
  return a + b;
}

export function subtract(a, b) {
  return a - b;
}

// app.js - используем модуль
import { add, subtract } from './math.js';

const result = add(5, 3); // 8

Модуль открывает public API через экспорты.

2. Named Exports (Именованные экспорты)

// user.service.js
export function getUser(id) {
  return fetch(`/api/users/${id}`).then(r => r.json());
}

export function updateUser(id, data) {
  return fetch(`/api/users/${id}`, {
    method: 'PUT',
    body: JSON.stringify(data)
  });
}

// component.jsx
import { getUser, updateUser } from './user.service';

function UserProfile({ userId }) {
  const [user, setUser] = React.useState(null);
  
  React.useEffect(() => {
    getUser(userId).then(setUser);
  }, [userId]);
  
  const handleUpdate = (data) => {
    updateUser(userId, data).then(setUser);
  };
  
  return <div>{user?.name}</div>;
}

3. Default Export (Стандартный экспорт)

// Button.jsx
export default function Button({ children, onClick }) {
  return <button onClick={onClick}>{children}</button>;
}

// app.jsx
import Button from './Button';

function App() {
  return <Button onClick={() => alert('Clicked')}>Click me</Button>;
}

4. Инкапсуляция с Closure

// counter.js - приватные данные
const Counter = (() => {
  let count = 0; // Приватная переменная
  
  return {
    increment() {
      return ++count;
    },
    decrement() {
      return --count;
    },
    getCount() {
      return count;
    }
  };
})();

// app.js - используем public методы
Counter.increment(); // 1
Counter.increment(); // 2
Counter.getCount();  // 2
// Counter.count недоступен!

5. Dependency Injection

// logger.js - модуль, требующий зависимость
function createLogger(storage) {
  return {
    log(message) {
      storage.save(message);
    }
  };
}

// app.js - внедряем зависимость
const fileStorage = {
  save(data) {
    console.log('Saving to file:', data);
  }
};

const logger = createLogger(fileStorage);
logger.log('Error occurred'); // Saving to file: Error occurred

6. Pub/Sub паттерн (Event Emitter)

// eventBus.js - отдельный модуль для коммуникации
class EventBus {
  constructor() {
    this.events = {};
  }
  
  on(event, callback) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(callback);
  }
  
  emit(event, data) {
    if (this.events[event]) {
      this.events[event].forEach(cb => cb(data));
    }
  }
}

export default new EventBus();

// Module A - отправляет событие
import eventBus from './eventBus';

function handleUserLogin(user) {
  eventBus.emit('user:login', user);
}

// Module B - слушает событие
import eventBus from './eventBus';

eventBus.on('user:login', (user) => {
  console.log(`Welcome, ${user.name}!`);
});

7. Context API (React)

// UserContext.js
const UserContext = React.createContext();

export function UserProvider({ children }) {
  const [user, setUser] = React.useState(null);
  
  return (
    <UserContext.Provider value={{ user, setUser }}>
      {children}
    </UserContext.Provider>
  );
}

export function useUser() {
  return React.useContext(UserContext);
}

// ProfileComponent.jsx - используем контекст
import { useUser } from './UserContext';

function Profile() {
  const { user } = useUser();
  return <div>{user?.name}</div>;
}

// LoginComponent.jsx
import { useUser } from './UserContext';

function Login() {
  const { setUser } = useUser();
  const handleLogin = () => {
    setUser({ id: 1, name: 'John' });
  };
  return <button onClick={handleLogin}>Login</button>;
}

8. Сервисный слой

// services/api.service.js
class ApiService {
  private baseUrl = 'https://api.example.com';
  
  async request(endpoint, options = {}) {
    const response = await fetch(`${this.baseUrl}${endpoint}`, options);
    return response.json();
  }
  
  getUsers() {
    return this.request('/users');
  }
  
  getUser(id) {
    return this.request(`/users/${id}`);
  }
}

export default new ApiService();

// components/UserList.jsx
import ApiService from '../services/api.service';

function UserList() {
  const [users, setUsers] = React.useState([]);
  
  React.useEffect(() => {
    ApiService.getUsers().then(setUsers);
  }, []);
  
  return (
    <ul>
      {users.map(user => <li key={user.id}>{user.name}</li>)}
    </ul>
  );
}

9. Хуки для переиспользуемой логики

// hooks/useApi.js
function useApi(url) {
  const [data, setData] = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  const [error, setError] = React.useState(null);
  
  React.useEffect(() => {
    fetch(url)
      .then(r => r.json())
      .then(setData)
      .catch(setError)
      .finally(() => setLoading(false));
  }, [url]);
  
  return { data, loading, error };
}

// components/Profile.jsx
import { useApi } from '../hooks/useApi';

function Profile({ userId }) {
  const { data: user, loading } = useApi(`/api/users/${userId}`);
  
  if (loading) return <p>Loading...</p>;
  return <div>{user?.name}</div>;
}

10. Props Drilling для простых случаев

// Родитель передаёт функцию дочерним компонентам
function Parent() {
  const [count, setCount] = React.useState(0);
  
  return <Child onIncrement={() => setCount(count + 1)} />;
}

function Child({ onIncrement }) {
  return <GrandChild onIncrement={onIncrement} />;
}

function GrandChild({ onIncrement }) {
  return <button onClick={onIncrement}>+1</button>;
}

11. Интерфейсы (TypeScript)

// types/User.ts
export interface IUserService {
  getUser(id: string): Promise<User>;
  updateUser(id: string, data: Partial<User>): Promise<User>;
  deleteUser(id: string): Promise<void>;
}

// services/UserService.ts
import { IUserService } from '../types/User';

export class UserService implements IUserService {
  async getUser(id: string) { /* ... */ }
  async updateUser(id: string, data) { /* ... */ }
  async deleteUser(id: string) { /* ... */ }
}

// components/UserForm.tsx
import type { IUserService } from '../types/User';

interface UserFormProps {
  userService: IUserService;
}

export function UserForm({ userService }: UserFormProps) {
  // Используем контракт userService
  return <div>...</div>;
}

12. Лучшие практики

Инкапсуляция

  • Экспортируй только необходимое API
  • Скрывай внутреннюю реализацию

Слабая связанность (Low coupling)

  • Зависимости через параметры, не глобальные переменные
  • Используй интерфейсы, не конкретные классы

Высокая когезия (High cohesion)

  • Группируй связанную функциональность в один модуль
  • Избегай смешивания разных ответственностей

Явные контракты

  • TypeScript типы документируют API
  • JSDoc комментарии описывают использование

Выводы

Взаимодействие с модулем извне основано на:

  • Экспортах/Импортах - основной механизм
  • Инкапсуляции - скрытие деталей реализации
  • Dependency Injection - явная передача зависимостей
  • Event Emitter - слабая связанность через события
  • Интерфейсы - контракты между модулями

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

Как взаимодействовать с модулем не находясь внутри него? | PrepBro