Какие паттерны проектирования часто применяются в React?

В экосистеме React активно применяются шаблоны (паттерны) проектирования, которые помогают решать типовые архитектурные задачи, делают компоненты переиспользуемыми, управляемыми и тестируемыми. Эти паттерны охватывают организацию компонентов, управление состоянием, передачу данных, изоляцию логики, композицию и масштабирование UI. Ниже приведено подробное описание наиболее распространённых паттернов, применяемых в React-проектах.

1. Container-Presenter (Smart/Dumb) Components

Разделяет компонент на два слоя:

  • Container (умный) — содержит бизнес-логику, состояние, API-запросы, управляет данными.

  • Presenter (глупый) — только отображает UI, не знает откуда пришли данные.

Пример:

// LoginContainer.jsx
import LoginForm from './LoginForm';
import { useAuth } from './useAuth';
function LoginContainer() {
const { handleLogin } = useAuth();
return <LoginForm onSubmit={handleLogin} />;
}
// LoginForm.jsx
function LoginForm({ onSubmit }) {
return (
<form onSubmit={onSubmit}>
<input name="email" />
<input name="password" />
<button type="submit">Login</button>
</form>
);
}

2. Render Props

Позволяет передавать поведение компоненту через функцию как пропс, обеспечивая повторное использование логики.

function MouseTracker({ render }) {
const \[position, setPosition\] = useState({ x: 0, y: 0 });
return (
<div onMouseMove={e => setPosition({ x: e.clientX, y: e.clientY })}>
{render(position)}
</div>
);
}
// Использование
<MouseTracker render={({ x, y }) => <p>Mouse at {x}, {y}</p>} />

3. Higher-Order Components (HOC)

Функции, принимающие компонент и возвращающие новый компонент с дополнительной логикой.

function withLoading(Component) {
return function WithLoadingComponent({ isLoading, ...props }) {
if (isLoading) return <div>Loading...</div>;
return <Component {...props} />;
};
}
// Применение
const UserListWithLoading = withLoading(UserList);

4. Controlled/Uncontrolled Components

Controlled Component — состояние передаётся через props, и родитель управляет значением.

<input value={value} onChange={e => setValue(e.target.value)} />

Uncontrolled Component — используется ref для доступа к значению, React не управляет состоянием.

<input ref={inputRef} />

5. Compound Components

Группа компонентов, работающих вместе, разделяя контекст и поведение.

<Tabs>
<Tabs.TabList>
<Tabs.Tab>One</Tabs.Tab>
<Tabs.Tab>Two</Tabs.Tab>
</Tabs.TabList>
<Tabs.TabPanels>
<Tabs.TabPanel>Content One</Tabs.TabPanel>
<Tabs.TabPanel>Content Two</Tabs.TabPanel>
</Tabs.TabPanels>
</Tabs>

Реализация часто использует контекст:

const TabsContext = React.createContext();
function Tabs({ children }) {
const \[active, setActive\] = useState(0);
return (
<TabsContext.Provider value={{ active, setActive }}>
{children}
</TabsContext.Provider>
);
}

6. Hooks как инкапсуляция логики

React-хуки позволяют изолировать бизнес-логику в переиспользуемые функции.

function useFetch(url) {
const \[data, setData\] = useState(null);
useEffect(() => {
fetch(url).then(res => res.json()).then(setData);
}, \[url\]);
return data;
}

Хуки — это реализация паттерна Strategy, где логика передаётся как поведение.

7. State Reducer Pattern

Компонент предоставляет возможность пользователю контролировать поведение через редьюсер, как в useReducer.

function Toggle({ reducer, initialState }) {
const \[state, dispatch\] = useReducer(reducer, initialState);
return <button onClick={() => dispatch({ type: 'toggle' })}>{state.on ? 'On' : 'Off'}</button>;
}

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

8. Controlled State Reducer (Headless UI)

Реализация компонента без визуала, предоставляющая только API. Пример — headless dropdown:

function useDropdown({ initialOpen = false }) {
const \[open, setOpen\] = useState(initialOpen);
const toggle = () => setOpen(o => !o);
return { open, toggle };
}

UI полностью отделяется от состояния. Пользователь сам реализует визуал, используя логику.

9. Provider Pattern (контексты)

Позволяет передавать данные глубоко по дереву без пропсов.

const ThemeContext = createContext();
function ThemeProvider({ children }) {
const theme = useThemeState(); // custom hook
return <ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>;
}
function Button() {
const { theme } = useContext(ThemeContext);
return <button className={theme}>Click</button>;
}

10. Facade Pattern через API-слои

Вместо обращения к внешнему API из компонентов напрямую, создаются фасадные слои:

// services/userService.ts
export async function fetchUser(id: string) {
const res = await axios.get(\`/api/users/${id}\`);
return res.data;
}

Компонент работает с этим слоем, не зная про REST, GraphQL и т.п.

11. Slot/Render Slot

Позволяет вставлять произвольные подкомпоненты внутрь основного компонента:

function Card({ header, footer, children }) {
return (
<div className="card">
<div>{header}</div>
<div>{children}</div>
<div>{footer}</div>
</div>
);
}
<Card
header={&lt;h1&gt;Title&lt;/h1&gt;}
footer={&lt;span&gt;Footer&lt;/span&gt;}
\>
&lt;p&gt;Content inside&lt;/p&gt;
&lt;/Card&gt;

12. Builder Pattern с JSX

React сам по себе реализует builder-подход, где интерфейс строится декларативно с помощью JSX, а дерево компонентов выступает как результат сборки.

&lt;Form&gt;
&lt;Input name="email" /&gt;
&lt;Input name="password" /&gt;
&lt;SubmitButton&gt;Login&lt;/SubmitButton&gt;
&lt;/Form&gt;

13. Observer Pattern через useEffect и подписки

Когда компонент подписывается на изменения внешнего источника (например, WebSocket, события DOM или стор Redux), он фактически реализует паттерн Наблюдатель:

useEffect(() => {
socket.on('message', handleMessage);
return () => socket.off('message', handleMessage);
}, \[\]);

14. Lazy Initialization / Lazy Loading (Code Splitting)

React поддерживает ленивую загрузку компонентов, позволяя разделять код:

const ProfilePage = React.lazy(() => import('./ProfilePage'));
&lt;Suspense fallback={<Loader /&gt;}>
&lt;ProfilePage /&gt;
&lt;/Suspense&gt;

Этот подход реализует паттерн Proxy/Virtual Proxy, откладывая создание объекта до момента использования.

15. Command Pattern через диспатчинг событий

Состояние изменяется через команды (dispatch({ type, payload })) — типичный пример useReducer, Redux, Zustand:

function reducer(state, action) {
switch (action.type) {
case 'increment': return { count: state.count + 1 };
default: return state;
}
}

Эти паттерны активно используются как в простых компонентах, так и в архитектуре крупных приложений. Правильное применение этих шаблонов упрощает поддержку проекта, облегчает тестирование, улучшает читаемость кода и способствует масштабируемости React-приложения.