Хочешь уверенно проходить IT-интервью?

Мы понимаем, как сложно подготовиться: стресс, алгоритмы, вопросы, от которых голова идёт кругом. Но с AI тренажёром всё гораздо проще.
💡 Почему Т1 тренажёр — это мастхэв?
- Получишь настоящую обратную связь: где затык, что подтянуть и как стать лучше
- Научишься не только решать задачи, но и объяснять своё решение так, чтобы интервьюер сказал: "Вау!".
- Освоишь все этапы собеседования, от вопросов по алгоритмам до диалога о твоих целях.
Зачем листать миллион туториалов? Просто зайди в Т1 тренажёр, потренируйся и уверенно удиви интервьюеров. Мы не обещаем лёгкой прогулки, но обещаем, что будешь готов!
Реклама. ООО «Смарт Гико», ИНН 7743264341. Erid 2VtzqwP8vqy
При разработке приложений на React разработчики часто сталкиваются с повторяющимися задачами. Для решения этих задач были разработаны оптимальные практики – универсальные шаблоны проектирования, которые предлагают структурированный подход к:
- организации компонентов;
- управлению состоянием приложения;
- обработке данных.
Использование шаблонов улучшает производительность кода, облегчает поддержку и масштабирование проектов. В этой статье мы рассмотрим наиболее важные и распространенные шаблоны проектирования в React:
- Контейнер и презентация
- Компонент высшего порядка
- Составные компоненты
- Провайдер
- Редуктор состояния
- Компоновщик
Контейнер и презентация
Этот паттерн разделяет компоненты на две категории: контейнеры и презентационные компоненты:
- Контейнеры управляют данными и логикой состояния. Они загружают данные из внешних источников, при необходимости их обрабатывают и передают в компоненты презентации через пропсы. Контейнеры часто связаны с внешними сервисами, хранилищами Redux или контекстными провайдерами.
- Презентационные компоненты сосредоточены исключительно на отображении элементов интерфейса. Они получают данные от контейнеров через пропсы и визуализируют их в соответствии с HTML-разметкой и стилями CSS. Обычно это статические функциональные компоненты, которые проще тестировать и переиспользовать.
В качестве примера можно представить панель управления соцсетью, где пользователи просматривают посты друзей и взаимодействуют с ними. Вот как можно структурировать компоненты:
Контейнерный компонент FriendFeedContainer
отвечает за получение данных о постах друзей из API, выполнение любых манипуляций с этими данными и управление состоянием ленты новостей. Затем он передает данные в соответствующие презентационные компоненты.
import React, { useState, useEffect } from 'react';
import FriendFeed from './FriendFeed';
const FriendFeedContainer = () => {
const [friendPosts, setFriendPosts] = useState([]);
useEffect(() => {
// Получение постов друзей из API
const fetchFriendPosts = async () => {
const posts = await fetch('https://api.example.com/friend-posts');
const data = await posts.json();
setFriendPosts(data);
};
fetchFriendPosts();
}, []);
return <FriendFeed posts={friendPosts} />;
};
export default FriendFeedContainer;
Презентационный компонент FriendFeed
отвечает за отображение постов друзей на экране. Он получает данные о постах от своего родительского контейнерного компонента FriendFeedContainer
через пропсы.
Получив данные, FriendFeed
форматирует текст, добавляет изображения, аватары пользователей и любые другие визуальные составляющие постов. По сути, этот компонент не знает, откуда берутся данные, он просто их отображает в привлекательном виде.
import React from 'react';
const FriendFeed = ({ posts }) => {
return (
<div>
<h2>Friend Feed</h2>
<ul>
{posts.map(post => (
<li key={post.id}>
<p>{post.content}</p>
<p>Posted by: {post.author}</p>
</li>
))}
</ul>
</div>
);
};
export default FriendFeed;
Компонент высшего порядка
Компонент высшего порядка (HOC – Higher-Order Component) – это способ повторного использования логики компонентов в React. HOC представляет собой функцию, которая принимает компонент в качестве аргумента и возвращает новый компонент с дополнительной функциональностью.
Возьмем пример панели управления соцсетью, созданной с помощью хуков. Представим, что у нас есть несколько компонентов, которым нужно получать данные о пользователях из API. Вместо того чтобы дублировать код получения данных в каждом компоненте, можно создать HOC для получения данных и передачи их обернутым компонентам в качестве пропсов:
import React, { useState, useEffect } from 'react';
// Определение компонента высшего порядка для получения данных
const withUserData = (WrappedComponent) => {
return (props) => {
const [userData, setUserData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Получение данных о пользователях из API
const fetchData = async () => {
try {
const response = await fetch('https://api.example.com/user');
const data = await response.json();
setUserData(data);
setLoading(false);
} catch (error) {
console.error('Error fetching user data:', error);
setLoading(false);
}
};
fetchData();
}, []);
return (
<div>
{loading ? (
<p>Loading...</p>
) : (
<WrappedComponent {...props} userData={userData} />
)}
</div>
);
};
};
// Создание компонента для отображения данных
const UserProfile = ({ userData }) => {
return (
<div>
<h2>User Profile</h2>
{userData && (
<div>
<p>Name: {userData.name}</p>
<p>Email: {userData.email}</p>
{/* Additional user data fields */}
</div>
)}
</div>
);
};
// Оборачивание компонента UserProfile в HOC withUserData
const UserProfileWithUserData = withUserData(UserProfile);
// Основной компонент, в котором можно рендерить содержимое обернутых компонентов
const SocialMediaDashboard = () => {
return (
<div>
<h1>Social Media Dashboard</h1>
<UserProfileWithUserData />
</div>
);
};
export default SocialMediaDashboard;
Этот паттерн позволяет повторно использовать логику получения данных во всех компонентах панели управления социальной сетью, не дублируя код:
- withUserData – компонент высшего порядка, который отвечает за получение пользовательских данных из API. Он оборачивает переданный компонент WrappedComponent и предоставляет ему полученные данные пользователя в качестве пропсов userData.
- UserProfile – функциональный компонент, который принимает пропсы userData и отображает информацию о профиле пользователя.
- UserProfileWithUserData – компонент, полученный путем обертывания UserProfile с помощью withUserData. Он наследует функциональность получения данных о пользователе от withUserData и отрисовывает профиль пользователя с помощью UserProfile.
- SocialMediaDashboard – основной компонент, в котором можно рендерить UserProfileWithUserData или любой другой компонент, которому нужны данные пользователя.
Хочу освоить современный стек и работать фронтенд-разработчиком. Что посоветуете?
Заглядывай на наш курс «Frontend Basic: принцип работы современного веба. С нуля до первого интернет-магазина» на котором ты освоишь HTML, CSS, JavaScript, React, Git. Курс состоит из 26 уроков и 28 домашних заданий. Наставник дает обратную связь. Для тех, кто хочет погрузиться в среду более углубленно, мы подготовили тариф с личным наставником.
Составные компоненты
Этот паттерн позволяет создавать компоненты, которые вместе формируют единый пользовательский интерфейс. При этом он обеспечивает четкое разделение ответственности и гибкость в настройке поведения и внешнего вида компонентов.
В этом шаблоне родительский компонент выступает контейнером для одного или нескольких дочерних компонентов, называемых составными компонентами. Эти дочерние компоненты взаимодействуют друг с другом для достижения определенной функциональности. Главная особенность составных компонентов заключается в том, что они совместно используют состояние и функциональность через свой родительский компонент. Такой подход упрощает разработку и помогает поддерживать чистоту кода.
Вот простой пример реализации паттерна составных компонентов в React с использованием хуков:
import React, { useState } from 'react';
// Родительский компонент, содержащий составные компоненты
const Toggle = ({ children }) => {
const [isOn, setIsOn] = useState(false);
// Функция переключения состояния
const toggle = () => {
setIsOn((prevIsOn) => !prevIsOn);
};
// Клонирование дочерних компонентов и передача им функции переключения и состояния
const childrenWithProps = React.Children.map(children, (child) => {
if (React.isValidElement(child)) {
return React.cloneElement(child, { isOn, toggle });
}
return child;
});
return <div>{childrenWithProps}</div>;
};
// Дочерний компонент для кнопки переключения
const ToggleButton = ({ isOn, toggle }) => {
return (
<button onClick={toggle}>
{isOn ? 'Turn Off' : 'Turn On'}
</button>
);
};
// Дочерний компонент для статуса переключения
const ToggleStatus = ({ isOn }) => {
return <p>The toggle is {isOn ? 'on' : 'off'}.</p>;
};
// Основной компонент, в котором используются составные компоненты
const App = () => {
return (
<Toggle>
<ToggleStatus />
<ToggleButton />
</Toggle>
);
};
export default App;
В этом примере:
- Toggle – родительский компонент, который объединяет составные компоненты ToggleButton и ToggleStatus.
- ToggleButton – дочерний компонент, отвечающий за отрисовку кнопки переключения.
- ToggleStatus – другой дочерний компонент, отвечающий за отображение состояния переключения.
- Компонент Toggle управляет состоянием
isOn
и предоставляет функциюtoggle
для его изменения. Он клонирует свои дочерние компоненты ToggleButton и ToggleStatus и передает им свое состояниеisOn
и функциюtoggle
в качестве пропсов.
Провайдер
Паттерн провайдер в React используется для управления и совместного использования состояния или данных приложения несколькими компонентами. Он подразумевает создание компонента-провайдера, который инкапсулирует состояние или данные и предоставляет их своим дочерним компонентам через контекст (context API). Такой подход позволяет обойтись без проброса пропсов и упрощает доступ к данным из любой части компонентного дерева.
Рассмотрим пример использования паттерна провайдера в React для управления данными пользователя во время авторизации:
// UserContext.js
import React, { createContext, useState } from 'react';
// Создание контекста для данных пользователя
const UserContext = createContext();
// Компонент провайдера
export const UserProvider = ({ children }) => {
const [user, setUser] = useState(null);
// Функция логина
const login = (userData) => {
setUser(userData);
};
// Функция выхода
const logout = () => {
setUser(null);
};
return (
<UserContext.Provider value={{ user, login, logout }}>
{children}
</UserContext.Provider>
);
};
export default UserContext;
В этом примере мы:
- Создаем контекст с названием UserContext при помощи функции
createContext
. Этот контекст будет использоваться для передачи данных пользователя и функций, связанных с авторизацией, между компонентами. - Определяем компонент UserProvider, который служит провайдером для UserContext. Этот компонент управляет состоянием пользователя с помощью хука
useState
и предоставляет методыlogin
иlogout
для обновления этого состояния. - Внутри UserProvider оборачиваем дочерние компоненты с помощью
UserContext.Provider
и передаем состояние пользователяuser
и функцииlogin
иlogout
в качестве значений. Теперь любой компонент, которому нужен доступ к данным пользователя или функциям, связанным с авторизацией, может использовать UserContext с помощью хука useContext.
Рассмотрим пример компонента, который получает данные пользователя из контекста:
// UserProfile.js
import React, { useContext } from 'react';
import UserContext from './UserContext';
const UserProfile = () => {
const { user, logout } = useContext(UserContext);
return (
<div>
{user ? (
<div>
<h2>Welcome, {user.username}!</h2>
<button onClick={logout}>Logout</button>
</div>
) : (
<div>
<h2>Please log in</h2>
</div>
)}
</div>
);
};
export default UserProfile;
В этом компоненте мы:
- Импортируем UserContext и используем хук useContext для доступа к данным пользователя
user
и функцииlogout
, предоставляемым UserProvider. - В зависимости от того, авторизован ли пользователь (
loggedIn
), отрисовываем разные элементы интерфейса. Например, если пользователь авторизован (loggedIn
имеет значениеtrue
), можно отобразить приветственное сообщение и кнопку выходаlogout
, а если не авторизован (loggedIn
имеет значениеfalse
), можно показать кнопку входа.
// App.js
import React from 'react';
import { UserProvider } from './UserContext';
import UserProfile from './UserProfile';
const App = () => {
return (
<UserProvider>
<div>
<h1>My App</h1>
<UserProfile />
</div>
</UserProvider>
);
};
export default App;
Редуктор состояния
Паттерн редуктор состояния – это контролируемый и предсказуемый способ управления состоянием React-приложения. Он использует функцию-редуктор для обработки изменений состояния и действий – точно так же, как это делают редукторы в Redux. Этот шаблон особенно полезен для управления сложной логикой состояния или состоянием, которое необходимо совместно использовать несколькими компонентами.
Рассмотрим пример реализации паттерна редуктора состояния для управления публикациями и уведомлениями пользователя в соцсети:
import React, { useReducer } from 'react';
// Типы действий
const ADD_POST = 'ADD_POST';
const DELETE_POST = 'DELETE_POST';
const ADD_NOTIFICATION = 'ADD_NOTIFICATION';
const DELETE_NOTIFICATION = 'DELETE_NOTIFICATION';
// Редуктор
const dashboardReducer = (state, action) => {
switch (action.type) {
case ADD_POST:
return { ...state, posts: [...state.posts, action.payload] };
case DELETE_POST:
return { ...state, posts: state.posts.filter(post => post.id !== action.payload.id) };
case ADD_NOTIFICATION:
return { ...state, notifications: [...state.notifications, action.payload] };
case DELETE_NOTIFICATION:
return { ...state, notifications: state.notifications.filter(notification => notification.id !== action.payload.id) };
default:
return state;
}
};
// Компонент панели управления
const Dashboard = () => {
// Initialize state using useReducer hook
const [state, dispatch] = useReducer(dashboardReducer, { posts: [], notifications: [] });
// Функция добавления поста
const addPost = (text) => {
const newPost = { id: Date.now(), text };
dispatch({ type: ADD_POST, payload: newPost });
};
// Функция удаления поста
const deletePost = (id) => {
dispatch({ type: DELETE_POST, payload: { id } });
};
// Функция уведомления
const addNotification = (text) => {
const newNotification = { id: Date.now(), text };
dispatch({ type: ADD_NOTIFICATION, payload: newNotification });
};
// Функция удаления
const deleteNotification = (id) => {
dispatch({ type: DELETE_NOTIFICATION, payload: { id } });
};
return (
<div>
<h1>Social Media Dashboard</h1>
<div>
<h2>Posts</h2>
<ul>
{state.posts.map(post => (
<li key={post.id}>
{post.text}
<button onClick={() => deletePost(post.id)}>Delete</button>
</li>
))}
</ul>
<button onClick={() => addPost('New post')}>Add Post</button>
</div>
<div>
<h2>Notifications</h2>
<ul>
{state.notifications.map(notification => (
<li key={notification.id}>
{notification.text}
<button onClick={() => deleteNotification(notification.id)}>Dismiss</button>
</li>
))}
</ul>
<button onClick={() => addNotification('New notification')}>Add Notification</button>
</div>
</div>
);
};
export default Dashboard;
В этом примере мы:
- Определяем ADD_POST, DELETE_POST, ADD_NOTIFICATION, DELETE_NOTIFICATION – действия, которые можно выполнять с данными в панели управления аккаунта.
- Определяем функцию-редуктор
dashboardReducer
, которая принимает текущее состояние и действие, а затем возвращает новое состояние на основе этого действия. - С помощью хука useReducer создаем значение состояния
state
и функциюdispatch
для обновления состояния путем передачи действий редуктору. - Компонент Dashboard содержит логику для добавления и удаления постов и уведомлений. Он вызывает
dispatch
с определенными действиями, чтобы обновить состояние соответствующим образом. - В интерфейсе посты и уведомления отображаются как элементы списка с кнопками для их удаления. Пользователи могут добавлять новые посты или уведомления, нажимая соответствующие кнопки.
Как очевидно из этого примера, функция-редуктор централизует логику управления состоянием, делая ее более понятной и поддерживаемой.
Компоновщик
Идея компоновщика заключается в объединении мелких, многоразовых компонентов для создания более сложных компонентов или интерфейсов. Давайте проиллюстрируем этот паттерн на примере панели управления в соцсети.
Предположим, эта панель управления состоит из нескольких компонентов – UserInfo (информация о пользователе), Feed (лента новостей), Notifications (уведомления) и Sidebar (боковая панель). Можно объединить эти мелкие компоненты вместе, чтобы создать единый компонент панели управления. Такой компонент панели управления может выглядеть следующим образом:
import React from 'react';
// информация о пользователе
const UserInfo = ({ user }) => {
return (
<div className="user-info">
<img src={user.profilePic} alt="Profile" />
<h3>{user.name}</h3>
</div>
);
};
// лента новостей
const Feed = ({ posts }) => {
return (
<div className="feed">
<h2>Feed</h2>
<ul>
{posts.map((post, index) => (
<li key={index}>
<h4>{post.title}</h4>
<p>{post.content}</p>
</li>
))}
</ul>
</div>
);
};
// уведомления
const Notifications = ({ notifications }) => {
return (
<div className="notifications">
<h2>Notifications</h2>
<ul>
{notifications.map((notification, index) => (
<li key={index}>
<p>{notification}</p>
</li>
))}
</ul>
</div>
);
};
// боковая панель
const Sidebar = () => {
return (
<div className="sidebar">
<ul>
<li>Home</li>
<li>Profile</li>
<li>Messages</li>
<li>Settings</li>
</ul>
</div>
);
};
// панель управления, состоящая из мелких компонентов
const SocialMediaDashboard = ({ user, posts, notifications }) => {
return (
<div className="social-media-dashboard">
<UserInfo user={user} />
<div className="main-content">
<Feed posts={posts} />
<Notifications notifications={notifications} />
</div>
<Sidebar />
</div>
);
};
// главный компонент
const App = () => {
// Sample data
const user = {
name: "John Doe",
profilePic: "https://via.placeholder.com/150",
};
const posts = [
{ title: "Post 1", content: "Content of post 1" },
{ title: "Post 2", content: "Content of post 2" },
{ title: "Post 3", content: "Content of post 3" },
];
const notifications = [
"You have a new friend request",
"Your post has been liked",
"You have a new message",
];
return (
<div>
<h1>Social Media Dashboard</h1>
<SocialMediaDashboard user={user} posts={posts} notifications={notifications} />
</div>
);
};
export default App;
В этом примере мы:
- Используем отдельные компоненты для UserInfo, Feed, Notifications и Sidebar. Каждый из них отвечает за отрисовку определенной части панели управления социальной сетью.
- Объединяем эти мелкие компоненты вместе в SocialMediaDashboard, чтобы создать весь интерфейс панели управления.
- Компонент App является точкой входа, где происходит передача данных (информации о пользователе, постов, уведомлений) в компонент SocialMediaDashboard.
Каждый компонент фокусируется на определенном аспекте интерфейса – такой код легко переиспользовать и поддерживать.
Другие полезные паттерны
Набор шаблонов, которые можно использовать при разработке React-приложений, не ограничивается перечисленными выше паттернами. Более полная коллекция паттернов представлена в репозитории книги React 18 Design Patterns and Best Practices, бесплатную версию которой можно скачать здесь.
При подготовке статьи использовалась публикация React Component Design Patterns.
Комментарии