Обработка реальных состояний интерфейса в React с использованием условной отрисовки
Узнайте, как создавать интерфейсы аутентификации, ролей, разрешений, загрузки, ошибок и состояния пустоты в React с помощью практических шаблонов условной отрисовки.
В предыдущей части вы рассмотрели основы условной отрисовки — использование простых условий для указания React, какой компонент отображать, другой компонент или вообще ничего.
Однако реальные приложения редко бывают настолько простыми:
isLoggedIn ? <Dashboard /> : <Login />
Рассмотрим настоящий панель управления. Пользователь может находиться в одном из следующих состояний:
- Вышел из системы
- В процессе входа в систему
- Вошел в систему
- Является администратором
- Является обычным пользователем
- У него отсутствует необходимое разрешение
- Ожидает поступления данных
- Сталкивается с ошибкой API
- Просматривает пустой набор результатов
Каждый из этих сценариев требует своего интерфейса. Именно здесь условная отрисовка перестает быть простым трюком и становится настоящим инструментом для структурирования приложения.
Давайте посмотрим, как это реализуется в коде React производственного уровня.
1. Рендеринг на основе аутентификации
Классическим примером использования является аутентификация. Представьте приложение с двумя возможными экранами:
Not Logged In
↓
Login Page
Logged In
↓
Dashboard
React может легко выбирать между ними:
function App() {
const isLoggedIn = true;
return (
<>
{isLoggedIn ? <Dashboard /> : <Login />}
</>
);
}
Однако в реальных приложениях обычно требуется третье состояние: loading, поскольку на подтверждение действительности токена пользователя может уйти некоторое время. Тогда последовательность действий выглядит так:
Checking Authentication
↓
Loading
↓
Authenticated?
↙ ↘
YES NO
↓ ↓
Dashboard Login
В коде это может выглядеть следующим образом:
function App() {
const isLoading = false;
const isLoggedIn = true;
if (isLoading) {
return <LoadingSpinner />;
}
return isLoggedIn
? <Dashboard />
: <Login />;
}
Вы увидите, что этот точно такой же паттерн используется во множестве производственных приложений на React.
2. Рендеринг на основе ролей
Аутентификация показывает, кто вошел в систему. Авторизация указывает, что именно этот человек имеет право делать.
В качестве примера возьмем инструмент управления сотрудниками. Администратор может видеть:
View Employees
Add Employee
Edit Employee
Delete Employee
В то время как обычный пользователь видит только:
View Employees
Вы можете условно отображать действия в зависимости от роли пользователя:
function EmployeeCard({ userRole }) {
return (
<div>
<h2>Employee Details</h2>
<button>View</button>
{userRole === "admin" && (
<>
<button>Edit</button>
<button>Delete</button>
</>
)}
</div>
);
}
Благодаря такой настройке элементы управления, доступные только администраторам, отображаются исключительно для них.
Важное замечание по безопасности
Условная отрисовка определяет, что будет отображаться в интерфейсе, но скрытие элемента от вида не заменяет настоящую безопасность. Например:
{isAdmin && <DeleteButton />}
Это не позволяет обычному пользователю увидеть кнопку, но сервер всё равно должен самостоятельно проверять, действительно ли запрос исходит от человека с правами на удаление данных. Рассмотрите ситуацию следующим образом:
Frontend
↓
Controls what users SEE
Backend
↓
Controls what users CAN DO
Никогда не используйте условную отрисовку на стороне клиента в качестве механизма авторизации.
3. Интерфейс на основе разрешений
Более крупные приложения часто требуют более высокой степени детализации, чем простые роли вроде:
Admin
User
Вместо этого вы можете определить конкретные разрешения, такие как:
CAN_VIEW_USERS
CAN_EDIT_USERS
CAN_DELETE_USERS
CAN_EXPORT_REPORT
Затем компоненты могут отображать каждое действие по отдельности в зависимости от имеющихся разрешений:
function UserActions({ permissions }) {
return (
<>
{permissions.includes("CAN_EDIT_USERS") && (
<button>Edit</button>
)}
{permissions.includes("CAN_DELETE_USERS") && (
<button>Delete</button>
)}
</>
);
}
Такой подход позволяет гораздо точнее контролировать то, что может делать каждый пользователь.
4. Состояния загрузки
Предположим, панель управления отправляет запрос к API, на обработку которого уходит две секунды. Что должно отображаться на экране в течение этого времени? Конечно, не пустая страница — вместо этого нужен индикатор загрузки:
if (loading) {
return <p>Loading products...</p>;
}
Общий алгоритм выглядит следующим образом:
API Request
↓
Loading = true
↓
Show Loader
↓
API Response
↓
Loading = false
↓
Show Content
Индикаторы загрузки делают приложение более отзывчивым даже при медленном соединении с сетью.
5. Индикаторы-шаблоны
Вместо простого текстового сообщения вроде:
Loading...
многие современные интерфейсы отображают заместитель в форме будущего контента — индикатор-шаблон. Например:
┌──────────────────────┐
│ █████████████ │
│ ███████ │
│ █████████████████ │
└──────────────────────┘
Как только возвращаются реальные данные, они заменяют местоопределитель:
┌──────────────────────┐
│ MacBook Air │
│ ₹99,999 │
│ ⭐⭐⭐⭐⭐ │
└──────────────────────┘
Основная логика React остается такой же простой:
return loading
? <ProductSkeleton />
: <ProductCard />;
Единственная реальная разница заключается в улучшении пользовательского опыта.
6. Статусы ошибок
Звонки к API не всегда проходят гладко.
Соединение может прерваться.
Сервер может выйти из строя.
Запрос может истечь по времени.
Вместо того чтобы позволить приложению выйти из строя или зависнуть, следует отображать статус ошибки.
if (error) {
return (
<div>
<h2>Something went wrong.</h2>
<button>Try Again</button>
</div>
);
}
Грамотное обработка сбоев — это отличительная черта интерфейсов высокого качества.
7. Загрузка + Ошибка + Успех
На практике эти три состояния почти всегда появляются одновременно.
function ProductList({
loading,
error,
products
}) {
if (loading) {
return <p>Loading...</p>;
}
if (error) {
return <p>Something went wrong.</p>;
}
return <Products products={products} />;
}
Вы можете представить этот процесс так:
Request
│
├── Loading → Loader
│
├── Failed → Error
│
└── Success → Data
Когда вы перейдете к вызовам API и функции useEffect позже в этой серии, вы увидите, что именно этот шаблон будет встречаться снова и снова.
8. Состояние пустоты
То, что запрос выполнен успешно, не гарантирует наличия данных для отображения.
Предположим, пользователь вводит что-то вроде:
"React Quantum Pizza Developer"
Сам вызов API завершается без ошибок.
Но результат может выглядеть так:
products.length === 0
Вместо того чтобы оставлять экран пустым, покажите пользователю что-то значимое.
if (products.length === 0) {
return (
<div>
<h2>No Products Found</h2>
<p>Try changing your search.</p>
</div>
);
}
Состояние пустоты имеет большое значение для качественного пользовательского опыта.
Загрузка, пустота и ошибка
Новые разработчики React часто путают эти три состояния, но они представляют собой совершенно разные ситуации.
LOADING
Data hasn't arrived yet.
EMPTY
Data arrived, but nothing exists.
ERROR
Something failed.
Качественное приложение учитывает все три состояния отдельно.
9. Множественные условия
Иногда результат обработки зависит от нескольких условий, сочетаемых вместе.
Рассмотрим следующий алгоритм:
Is User Logged In?
↓
Is Subscription Active?
↓
Is User Admin?
↓
Show Admin Dashboard
Хочется собрать всё это в один огромный вложенный тернарный оператор:
condition1
? condition2
? condition3
? <A />
: <B />
: <C />
: <D />
Он компилируется без проблем.
Но его очень трудно читать.
Лучший подход — чётко разделить каждое условие.
if (!isLoggedIn) {
return <Login />;
}
if (!hasSubscription) {
return <UpgradePlan />;
}
if (isAdmin) {
return <AdminDashboard />;
}
return <UserDashboard />;
Эта версия гораздо проще для понимания.
10. Предохранительные конструкции
У этого паттерна есть название: предохранительные конструкции, также известные как досрочные возвраты.
Вместо того чтобы вкладывать условия одно внутрь другого:
if
└── if
└── if
└── UI
сначала решите проблемы крайних случаев и сразу вернитесь.
if (loading) return <Loader />;
if (error) return <ErrorPage />;
if (!user) return <Login />;
return <Dashboard />;
Результат получается чистым.
Он легко читаем.
К тому же его гораздо проще отлаживать.
Думайте как разработчик React
Прежде чем писать компонент, полезно спросить себя:
«Какие всего могут быть состояния этого экрана?»
Для страницы, формируемой по запросу API, этот список может выглядеть так:
Loading
Error
Empty
Success
Для процесса аутентификации он может выглядеть так:
Logged Out
Checking Authentication
Logged In
Unauthorized
Планирование этих состояний до начала написания кода значительно упрощает понимание готового компонента.
Распространённые ошибки начинающих
Накопление вложенных тернарных операций
Не жертвуйте читаемостью ради экономии нескольких строк кода.
Игнорирование состояния пустоты
Возвращение пустого массива API — это не ошибка; рассматривайте это как отдельный случай.
Путаница между скрытием UI и реальной безопасностью
Скрытие чего-то вроде:
<DeleteButton />
ничего не мешает кому-либо напрямую обратиться к вашему концу API.
Настоящая авторизация должна реализовываться на серверной части.
Позволение && отображать неверный контент
Будьте осторожны с кодом вроде:
{items.length && <ProductList />}
Если значение items.length совпадает с 0, React может отобразить:
0
именно это на странице.
Более безопасная версия — это:
{items.length > 0 && <ProductList />}
Теперь условие возвращает настоящее логическое значение.
Лучшие практики
При условном отображении читаемость всегда должна быть на первом месте.
Преферируйте такие подходы:
if (loading) return <Loader />;
вместо накопления условий в глубоко вложенном JSX.
Для состояний, которые отображаются часто, вынесите их в отдельные компоненты с возможностью повторного использования:
<Loader />
<ErrorMessage />
<EmptyState />
По мере увеличения сложности компонентов разделяйте бизнес-логику от того, что фактически отображается.
И не ограничивайтесь только оптимальным сценарием — планируйте все возможные состояния интерфейса.
Мини-проект: Умная панель управления
В качестве практики попробуйте создать панель управления, которая будет учитывать такие случаи:
User Not Logged In
↓
Login ScreenUser
Logged In
↓
Loading Dashboard
↓
┌──────┴──────┐
Error Success
↓ ↓
Error UI Data Exists?
↙ ↘
YES NO
↓ ↓
Dashboard Empty State
Затем добавьте поведение, зависящее от роли пользователя:
Admin
↓
Edit + Delete
User
↓
View Only
Подобный проект одновременно объединяет несколько концепций:
- Параметры
- Состояние
- События
- Условная отрисовка
Именно так отдельные элементы React начинают взаимодействовать друг с другом, когда вы создаете реальное приложение.
Вопросы на собеседовании
Что такое условная отрисовка?
Это практика отображения разного интерфейса в зависимости от текущего состояния или условий в приложении.
В чем разница между && и тернарным оператором?
Используйте &&, когда хотите, чтобы элемент отображался только в случае истинности условия, при этом в остальных случаях ничего не должно появляться. Используйте тернарный оператор, когда вам нужно разное отображение как для истинных, так и для ложных условий.
Что считается пустым состоянием?
Это интерфейс, который отображается, когда запрос выполнен успешно, но данных для показа просто нет.
Достаточно ли рендеринга на основе ролей на фронтенде для обеспечения безопасности?
Нет. Это удобство интерфейса — фактические проверки авторизации всё равно должны происходить на бэкенде.
Какова польза от ранних возвратов?
Они сокращают уровень вложенности, что делает условные компоненты более легкими для чтения и обслуживания.
Основные выводы
На этом этапе вы рассмотрели весь спектр условного рендеринга в React.
Вы видели, как реальные приложения обрабатывают варианты с авторизованным и незавершенным входом, ограничивают отображаемый контент в зависимости от роли, активируют функции с помощью детальных разрешений, показывают индикаторы загрузки данных, используют шаблоны-заглушки вместо обычного текста, отображают сообщения об ошибках при сбоях запросов, уведомляют пользователей, когда набор результатов пуст, объединяют несколько условий в единый логический поток, упрощают вложенные проверки с помощью защитных конструкций, а также применяют общие практики, сохраняющие эту логику поддерживаемой в реальном кодовом базисе.
Условная отрисовка позволяет вашему приложению предоставлять правильный опыт в зависимости от текущей ситуации.
Но впереди всё ещё есть сложность.
Предположим, API возвращает:
1,000 Products
Действительно ли вы будете писать:
<Product />
<Product />
<Product />
...
тысячу раз по отдельности?
Очевидно, что нет.
У React есть гораздо лучший способ решения этой проблемы.
В Части 9A вы узнаете, как отображать списки с помощью map(), и увидите, как один компонент может генерировать сотни или тысячи элементов интерфейса непосредственно на основе ваших данных.
Сразу после этого вы рассмотрите один из самых классических вопросов на собеседованиях по React:
Почему React требует наличия
key?
Увидимся в Части 9A — Отображение списков и ключей в React.
Связанная литература
- React 19.2 Explained: Activity, useEffectEvent, and Static Rendering — Узнайте, как новый компонент Activity, хук useEffectEvent и частичная статическая отрисовка в React 19.2 устраняют скрытые проблемы с производительностью в современных интерфейсах.