← React/Условный рендеринг#263 из 383← ПредыдущийСледующий →+15 XP
Полезно по теме:Гайд: React или VueПрактика: React setТермин: React HooksТема: React: хуки и экосистема

Условный рендеринг

Основные паттерны

Условный рендеринг в React — это обычный JavaScript: if/else, тернарный оператор, &&. Никакого специального синтаксиса (в отличие от Vue v-if).

1. Тернарный оператор (самый частый)

function LoginButton({ isLoggedIn }) {
  return (
    <div>
      {isLoggedIn
        ? <button>Выйти</button>
        : <button>Войти</button>
      }
    </div>
  )
}

2. Оператор && (показать или ничего)

function Notifications({ count }) {
  return (
    <div>
      <span>Уведомления</span>
      {count > 0 && <span className="badge">{count}</span>}
      {/* Если count === 0, ничего не рендерится */}
    </div>
  )
}

Ловушка с &&: если левая часть 0 (число), React рендерит ноль!

// БАГИ:
{items.length && <List items={items} />}  // Если length = 0, рендерит "0"!

// ПРАВИЛЬНО:
{items.length > 0 && <List items={items} />}  // явное boolean
{!!items.length && <List items={items} />}     // двойное отрицание

3. Переменная с JSX

function Alert({ type, message }) {
  let icon
  if (type === 'error') icon = <ErrorIcon />
  else if (type === 'warning') icon = <WarningIcon />
  else icon = <InfoIcon />

  return (
    <div className={`alert alert-${type}`}>
      {icon}
      <span>{message}</span>
    </div>
  )
}

4. Ранний возврат (Early Return)

Самый читаемый способ для сложных условий:

function UserProfile({ user, isLoading, error }) {
  if (isLoading) return <Spinner />
  if (error) return <ErrorMessage text={error} />
  if (!user) return <p>Пользователь не найден</p>

  // Основной рендер — только если всё хорошо
  return (
    <div className="profile">
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  )
}

5. switch для множества состояний

function OrderStatus({ status }) {
  switch (status) {
    case 'pending':   return <Badge color="yellow">Ожидает</Badge>
    case 'shipping':  return <Badge color="blue">Доставляется</Badge>
    case 'delivered': return <Badge color="green">Доставлено</Badge>
    case 'cancelled': return <Badge color="red">Отменён</Badge>
    default:          return <Badge color="gray">Неизвестно</Badge>
  }
}

Состояния загрузки, ошибки и пустых данных

Любой компонент с асинхронными данными должен обрабатывать три состояния:

function DataList({ data, isLoading, error }) {
  // 1. Загрузка
  if (isLoading) {
    return <div className="spinner">Загрузка...</div>
  }

  // 2. Ошибка
  if (error) {
    return <div className="error">Ошибка: {error.message}</div>
  }

  // 3. Пустые данные
  if (!data || data.length === 0) {
    return <div className="empty">Список пуст</div>
  }

  // 4. Данные есть
  return (
    <ul>
      {data.map(item => <li key={item.id}>{item.name}</li>)}
    </ul>
  )
}

TypeScript: discriminated union для состояния

// Discriminated union — безопасное представление состояний
type AsyncState<T> =
  | { status: 'loading' }
  | { status: 'error'; error: string }
  | { status: 'success'; data: T }
  | { status: 'empty' }

function DataDisplay({ state }: { state: AsyncState<User[]> }) {
  switch (state.status) {
    case 'loading':  return <Spinner />
    case 'error':    return <Error message={state.error} />
    case 'empty':    return <Empty text="Нет данных" />
    case 'success':  return <UserList users={state.data} />
  }
}

TypeScript проверит что все случаи обработаны (exhaustive check).

Сравнение с Vue

<!-- Vue — директивы v-if / v-else / v-show -->
<div v-if="isLoading">Загрузка...</div>
<div v-else-if="error">Ошибка</div>
<div v-else>{{ data }}</div>
// React — чистый JavaScript
{isLoading ? <div>Загрузка...</div>
  : error ? <div>Ошибка</div>
  : <div>{data}</div>}

Vue v-show (скрывает через CSS, не удаляет DOM) в React реализуется через style={{ display: isVisible ? 'block' : 'none' }} или CSS-класс.

Примеры

Компонент с четырьмя состояниями: загрузка, ошибка, пустые данные, данные — три подхода к условному рендерингу

// Реализуем компонент DataDisplay с четырьмя состояниями
// на чистом JS, демонстрируя три паттерна условного рендеринга

// ============================================================
// Подход 1: if/else — самый читаемый для сложной логики
// ============================================================

function DataDisplay_IfElse(state) {
  // Аналог React:
  // if (state.status === 'loading') return <Spinner />
  // if (state.status === 'error') return <Error message={state.error} />
  // if (state.status === 'empty') return <Empty />
  // return <DataList data={state.data} />

  if (state.status === 'loading') {
    return { type: 'spinner', props: { text: 'Загрузка...' } }
  }
  if (state.status === 'error') {
    return { type: 'error', props: { message: state.error } }
  }
  if (state.status === 'empty') {
    return { type: 'empty', props: { text: 'Список пуст' } }
  }
  return { type: 'data-list', props: { items: state.data } }
}

// ============================================================
// Подход 2: тернарный оператор — компактно для двух состояний
// ============================================================

function StatusBadge(status) {
  // React JSX:
  // return (
  //   <span className={status === 'active' ? 'badge-green' : 'badge-red'}>
  //     {status === 'active' ? 'Активен' : 'Неактивен'}
  //   </span>
  // )

  const isActive = status === 'active'
  return {
    type: 'span',
    props: {
      className: isActive ? 'badge-green' : 'badge-red',
      children: isActive ? 'Активен' : 'Неактивен'
    }
  }
}

// ============================================================
// Подход 3: && оператор — показать или ничего
// ============================================================

function NotificationPanel(count) {
  // React JSX:
  // return (
  //   <div>
  //     <h2>Уведомления</h2>
  //     {count > 0 && <span className="badge">{count}</span>}
  //     {count > 0 && <button>Очистить все</button>}
  //   </div>
  // )

  // Ловушка: count && <badge> — если count=0, рендерит "0"!
  // Правильно: count > 0 && <badge>
  const badgeEl = count > 0 ? { type: 'span', props: { children: count } } : null
  const clearBtn = count > 0 ? { type: 'button', props: { children: 'Очистить все' } } : null

  return {
    type: 'div',
    props: { children: [
      { type: 'h2', props: { children: 'Уведомления' } },
      badgeEl,   // null — React пропустит
      clearBtn   // null — React пропустит
    ].filter(Boolean) }  // убираем null
  }
}

// ============================================================
// Демонстрация всех состояний
// ============================================================

const states = [
  { status: 'loading' },
  { status: 'error', error: 'Сеть недоступна' },
  { status: 'empty' },
  { status: 'success', data: [{ id: 1, name: 'React' }, { id: 2, name: 'Vue' }] },
]

console.log('=== DataDisplay с разными состояниями ===')
states.forEach(state => {
  const result = DataDisplay_IfElse(state)
  console.log(`[${state.status}] -> type: ${result.type}`)
})

console.log('\n=== StatusBadge ===')
console.log(StatusBadge('active').props.className)    // 'badge-green'
console.log(StatusBadge('inactive').props.children)   // 'Неактивен'

console.log('\n=== NotificationPanel ===')
console.log(NotificationPanel(5).props.children.length)  // 3 (h2 + badge + button)
console.log(NotificationPanel(0).props.children.length)  // 1 (только h2)
// Если бы использовали 0 && badge — получили бы "0" в DOM!

Условный рендеринг

Основные паттерны

Условный рендеринг в React — это обычный JavaScript: if/else, тернарный оператор, &&. Никакого специального синтаксиса (в отличие от Vue v-if).

1. Тернарный оператор (самый частый)

function LoginButton({ isLoggedIn }) {
  return (
    <div>
      {isLoggedIn
        ? <button>Выйти</button>
        : <button>Войти</button>
      }
    </div>
  )
}

2. Оператор && (показать или ничего)

function Notifications({ count }) {
  return (
    <div>
      <span>Уведомления</span>
      {count > 0 && <span className="badge">{count}</span>}
      {/* Если count === 0, ничего не рендерится */}
    </div>
  )
}

Ловушка с &&: если левая часть 0 (число), React рендерит ноль!

// БАГИ:
{items.length && <List items={items} />}  // Если length = 0, рендерит "0"!

// ПРАВИЛЬНО:
{items.length > 0 && <List items={items} />}  // явное boolean
{!!items.length && <List items={items} />}     // двойное отрицание

3. Переменная с JSX

function Alert({ type, message }) {
  let icon
  if (type === 'error') icon = <ErrorIcon />
  else if (type === 'warning') icon = <WarningIcon />
  else icon = <InfoIcon />

  return (
    <div className={`alert alert-${type}`}>
      {icon}
      <span>{message}</span>
    </div>
  )
}

4. Ранний возврат (Early Return)

Самый читаемый способ для сложных условий:

function UserProfile({ user, isLoading, error }) {
  if (isLoading) return <Spinner />
  if (error) return <ErrorMessage text={error} />
  if (!user) return <p>Пользователь не найден</p>

  // Основной рендер — только если всё хорошо
  return (
    <div className="profile">
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  )
}

5. switch для множества состояний

function OrderStatus({ status }) {
  switch (status) {
    case 'pending':   return <Badge color="yellow">Ожидает</Badge>
    case 'shipping':  return <Badge color="blue">Доставляется</Badge>
    case 'delivered': return <Badge color="green">Доставлено</Badge>
    case 'cancelled': return <Badge color="red">Отменён</Badge>
    default:          return <Badge color="gray">Неизвестно</Badge>
  }
}

Состояния загрузки, ошибки и пустых данных

Любой компонент с асинхронными данными должен обрабатывать три состояния:

function DataList({ data, isLoading, error }) {
  // 1. Загрузка
  if (isLoading) {
    return <div className="spinner">Загрузка...</div>
  }

  // 2. Ошибка
  if (error) {
    return <div className="error">Ошибка: {error.message}</div>
  }

  // 3. Пустые данные
  if (!data || data.length === 0) {
    return <div className="empty">Список пуст</div>
  }

  // 4. Данные есть
  return (
    <ul>
      {data.map(item => <li key={item.id}>{item.name}</li>)}
    </ul>
  )
}

TypeScript: discriminated union для состояния

// Discriminated union — безопасное представление состояний
type AsyncState<T> =
  | { status: 'loading' }
  | { status: 'error'; error: string }
  | { status: 'success'; data: T }
  | { status: 'empty' }

function DataDisplay({ state }: { state: AsyncState<User[]> }) {
  switch (state.status) {
    case 'loading':  return <Spinner />
    case 'error':    return <Error message={state.error} />
    case 'empty':    return <Empty text="Нет данных" />
    case 'success':  return <UserList users={state.data} />
  }
}

TypeScript проверит что все случаи обработаны (exhaustive check).

Сравнение с Vue

<!-- Vue — директивы v-if / v-else / v-show -->
<div v-if="isLoading">Загрузка...</div>
<div v-else-if="error">Ошибка</div>
<div v-else>{{ data }}</div>
// React — чистый JavaScript
{isLoading ? <div>Загрузка...</div>
  : error ? <div>Ошибка</div>
  : <div>{data}</div>}

Vue v-show (скрывает через CSS, не удаляет DOM) в React реализуется через style={{ display: isVisible ? 'block' : 'none' }} или CSS-класс.

Примеры

Компонент с четырьмя состояниями: загрузка, ошибка, пустые данные, данные — три подхода к условному рендерингу

// Реализуем компонент DataDisplay с четырьмя состояниями
// на чистом JS, демонстрируя три паттерна условного рендеринга

// ============================================================
// Подход 1: if/else — самый читаемый для сложной логики
// ============================================================

function DataDisplay_IfElse(state) {
  // Аналог React:
  // if (state.status === 'loading') return <Spinner />
  // if (state.status === 'error') return <Error message={state.error} />
  // if (state.status === 'empty') return <Empty />
  // return <DataList data={state.data} />

  if (state.status === 'loading') {
    return { type: 'spinner', props: { text: 'Загрузка...' } }
  }
  if (state.status === 'error') {
    return { type: 'error', props: { message: state.error } }
  }
  if (state.status === 'empty') {
    return { type: 'empty', props: { text: 'Список пуст' } }
  }
  return { type: 'data-list', props: { items: state.data } }
}

// ============================================================
// Подход 2: тернарный оператор — компактно для двух состояний
// ============================================================

function StatusBadge(status) {
  // React JSX:
  // return (
  //   <span className={status === 'active' ? 'badge-green' : 'badge-red'}>
  //     {status === 'active' ? 'Активен' : 'Неактивен'}
  //   </span>
  // )

  const isActive = status === 'active'
  return {
    type: 'span',
    props: {
      className: isActive ? 'badge-green' : 'badge-red',
      children: isActive ? 'Активен' : 'Неактивен'
    }
  }
}

// ============================================================
// Подход 3: && оператор — показать или ничего
// ============================================================

function NotificationPanel(count) {
  // React JSX:
  // return (
  //   <div>
  //     <h2>Уведомления</h2>
  //     {count > 0 && <span className="badge">{count}</span>}
  //     {count > 0 && <button>Очистить все</button>}
  //   </div>
  // )

  // Ловушка: count && <badge> — если count=0, рендерит "0"!
  // Правильно: count > 0 && <badge>
  const badgeEl = count > 0 ? { type: 'span', props: { children: count } } : null
  const clearBtn = count > 0 ? { type: 'button', props: { children: 'Очистить все' } } : null

  return {
    type: 'div',
    props: { children: [
      { type: 'h2', props: { children: 'Уведомления' } },
      badgeEl,   // null — React пропустит
      clearBtn   // null — React пропустит
    ].filter(Boolean) }  // убираем null
  }
}

// ============================================================
// Демонстрация всех состояний
// ============================================================

const states = [
  { status: 'loading' },
  { status: 'error', error: 'Сеть недоступна' },
  { status: 'empty' },
  { status: 'success', data: [{ id: 1, name: 'React' }, { id: 2, name: 'Vue' }] },
]

console.log('=== DataDisplay с разными состояниями ===')
states.forEach(state => {
  const result = DataDisplay_IfElse(state)
  console.log(`[${state.status}] -> type: ${result.type}`)
})

console.log('\n=== StatusBadge ===')
console.log(StatusBadge('active').props.className)    // 'badge-green'
console.log(StatusBadge('inactive').props.children)   // 'Неактивен'

console.log('\n=== NotificationPanel ===')
console.log(NotificationPanel(5).props.children.length)  // 3 (h2 + badge + button)
console.log(NotificationPanel(0).props.children.length)  // 1 (только h2)
// Если бы использовали 0 && badge — получили бы "0" в DOM!

Задание

Создай компонент App, который показывает профиль пользователя с тремя состояниями. Используй useState для хранения булевого isLoggedIn и объекта user. Если isLoggedIn равно false — показывай кнопку "Войти". Если isLoggedIn равно true — показывай имя и email пользователя и кнопку "Выйти". При клике на "Войти" переключай isLoggedIn в true, при "Выйти" — в false.

Подсказка

useState(false) — начальное значение не авторизован. Тернарный оператор: {isLoggedIn ? <блок авторизован> : <блок не авторизован>}. "Войти": setIsLoggedIn(true). "Выйти": setIsLoggedIn(false). Или используй !isLoggedIn для переключения.

Загружаем среду выполнения...
Загружаем AI-помощника...