Какие паттерны проектирования часто применяются в 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={<h1>Title</h1>}
footer={<span>Footer</span>}
\>
<p>Content inside</p>
</Card>
12. Builder Pattern с JSX
React сам по себе реализует builder-подход, где интерфейс строится декларативно с помощью JSX, а дерево компонентов выступает как результат сборки.
<Form>
<Input name="email" />
<Input name="password" />
<SubmitButton>Login</SubmitButton>
</Form>
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'));
<Suspense fallback={<Loader />}>
<ProfilePage />
</Suspense>
Этот подход реализует паттерн 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-приложения.