Skip to content

Модули

Раздел описывает модули SLM: что такое модуль, из чего он состоит и как взаимодействует с остальным кодом.

Определение

Модуль — универсальный строительный блок архитектуры. Живёт на слое и содержит всё необходимое для своей работы: компоненты, хуки, сторы, сервисы, типы, стили. Набор содержимого не фиксирован — включаются только нужные части.

Модуль vs компонент

Компонент — один .tsx файл. Не имеет своих сегментов, использует сегменты родительского модуля. Живёт в корне или ui/ сегменте модуля.

Модуль — папка, которая может содержать корневой компонент, сегменты (hooks/, types/, styles/, ui/, parts/ и т.д.) и публичный API (index.ts).

text
auth/                              
├── ui/
│   ├── auth-guard.tsx
│   └── logout-button.tsx
├── parts/
│   ├── login-form/
│   ├── registration-form/
│   └── restore-form/
├── hooks/
├── stores/
├── types/
├── auth.tsx             # корневой компонент (опционален)
└── index.ts

Структура

Модуль состоит из сегментов. Ни один сегмент не обязателен — модуль может состоять даже из одного index.ts с реэкспортом типов.

text
{module-name}/
├── {module-name}.tsx            # корневой компонент (опционален)
├── ui/                          # компоненты модуля (только .tsx)
├── parts/                       # вложенные модули (со своими сегментами)
├── hooks/                       # хуки
├── stores/                      # сторы состояния
├── services/                    # внешние источники данных
├── mappers/                     # трансформация данных между форматами
├── types/                       # типы
├── styles/                      # стили
├── lib/                         # утилиты модуля
├── config/                      # константы
└── index.ts                     # публичный API

Подробное описание каждого сегмента — в разделе Сегменты.

Публичный API

Модуль экспортирует наружу только то, что нужно другим. Всё остальное — внутреннее.

ts
// business/auth/index.ts
export type { User, Session } from './types/user.types'
export { useAuth } from './hooks/use-auth.hook'
export { AuthGuard } from './ui/auth-guard'

Импорт в обход index.ts запрещён:

ts
// Плохо
import { validateToken } from '@/business/auth/lib/tokens'

// Хорошо
import { useAuth } from '@/business/auth'

Фабрика

Если модуль зависит от кода другого бизнес-домена — он экспортирует фабрику. Фабрика декларирует необходимые зависимости и возвращает API модуля. Точка использования (screen, widget, layout) предоставляет зависимости при вызове.

Модуль без cross-domain зависимостей экспортирует API напрямую. Типы всегда экспортируются напрямую — import type не является runtime-зависимостью.

Модуль без зависимостей — прямой экспорт:

ts
// business/auth/index.ts
export { useAuth } from './hooks/use-auth'
export { useCurrentUser } from './hooks/use-current-user'
export type { User, Session } from './types'

Модуль с зависимостями — фабрика:

ts
// business/chat/types/deps.ts
import type { User } from '@/business/auth'

export interface ChatDeps {
  useCurrentUser: () => User | null
}
ts
// business/chat/index.ts
import type { ChatDeps } from './types/deps'

export function chatFactory(deps: ChatDeps) {
  return {
    useMessages: (roomId: string) => {
      const user = deps.useCurrentUser()
      // ...
    },
    useSendMessage: (roomId: string) => {
      const user = deps.useCurrentUser()
      return (text: string) => { /* ... */ }
    },
    useChatRooms: () => {
      const user = deps.useCurrentUser()
      // ...
    },
    ChatBadge: ({ count }: { count: number }) => { /* ... */ },
  }
}

export type { Message, ChatRoom } from './types'
export type { ChatDeps } from './types/deps'

Использование на странице:

tsx
// screens/support/support.tsx
import { useCurrentUser } from '@/business/auth'
import { chatFactory } from '@/business/chat'

const chat = chatFactory({ useCurrentUser })

export function SupportScreen() {
  const { useMessages, useSendMessage, ChatBadge } = chat
  const messages = useMessages('support')
  const sendMessage = useSendMessage('support')

  return (
    <div>
      <ChatBadge count={messages.length} />
      {messages.map(m => <MessageBubble key={m.id} {...m} />)}
      <MessageInput onSend={sendMessage} />
    </div>
  )
}

Жизненный цикл

Модуль рождается на самом низком уровне использования и поднимается выше только при реальной потребности.

  • Нужен на одной странице → screens/{name}/parts/
  • Появился в 2+ местах → поднимается по природе:
    • абстрактный UI → ui/
    • блок с данными/логикой → widgets/
    • представление бизнес-домена → business/{area}/parts/

Подъём — обычный рефакторинг в рамках задачи, а не отдельная активность.