Back

Распространённые ошибки при работе с JSX и способы их избежать

Распространённые ошибки при работе с JSX и способы их избежать

JSX выглядит обманчиво просто — это всего лишь HTML в JavaScript, верно? Тем не менее, даже опытные разработчики спотыкаются о его особенности, особенно по мере развития React. С автоматическим JSX runtime в React 19, Server Components и меняющимся ландшафтом современных фреймворков эти ошибки приобретают новые последствия. Вот что до сих пор сбивает с толку разработчиков и как избежать этих подводных камней.

Ключевые выводы

  • Индексы массивов в качестве ключей вызывают проблемы согласования и нарушают работу конкурентных функций React
  • Server Components требуют других паттернов по сравнению с клиентскими компонентами, особенно при работе с браузерными API
  • Автоматический JSX runtime изменяет способ трансформации вашего кода и требует правильной конфигурации
  • Inline-функции и паттерны условного рендеринга могут незаметно снижать производительность

Эволюция JSX: почему старые привычки ломают новый код

Автоматический JSX runtime, представленный в React 17, устранил необходимость импортировать React в каждом файле, но также создал новую путаницу. Теперь ваш JSX трансформируется по-другому — функции jsx заменяют React.createElement, а неправильно настроенные инструменты сборки могут незаметно сломать ваше приложение.

В Server Components ставки выше. JSX, который прекрасно работает на клиенте, падает с ошибкой, когда пытается обратиться к window или использует хуки в неправильном контексте. Правила не просто изменились; их стало больше.

Критические подводные камни JSX в современном React

1. Нестабильные ключи, разрушающие производительность

// ❌ Индекс в качестве ключа - вызывает проблемы согласования
items.map((item, index) => <Item key={index} {...item} />)

// ✅ Стабильный, уникальный идентификатор
items.map(item => <Item key={item.id} {...item} />)

Использование индексов массива в качестве ключей остаётся одной из самых разрушительных ошибок JSX. В конкурентных функциях React нестабильные ключи не просто вызывают мерцание — они могут нарушать границы Suspense и инициировать ненужные повторные рендеры по всему дереву компонентов.

2. Прямой рендеринг объектов

// ❌ Объекты не являются валидными дочерними элементами React
const user = { name: 'Alice', age: 30 };
return <div>{user}</div>;

// ✅ Рендерим конкретные свойства
return <div>{user.name}</div>;

Это сообщение об ошибке не менялось с React 15, однако разработчики всё ещё пытаются рендерить объекты напрямую. С выводом типов JSX в TypeScript вы поймаете это на этапе компиляции — если ваш tsconfig.json правильно настроен.

3. Inline-функции, создающие новые ссылки

// ❌ Новая функция при каждом рендере
<Button onClick={() => handleClick(id)} />

// ✅ Стабильная ссылка с useCallback
const handleButtonClick = useCallback(() => handleClick(id), [id]);
<Button onClick={handleButtonClick} />

В конвейере рендеринга React inline-функции не просто вызывают проблемы с производительностью — они нарушают оптимизацию memo и могут запускать каскадные обновления по всему дереву компонентов.

Server Components: где правила JSX меняются

4. Клиентский код в Server Components

// ❌ Падает с ошибкой в Server Components
export default function ServerComponent() {
  const width = window.innerWidth; // ReferenceError
  return <div style={{ width }} />;
}

// ✅ Используйте директиву client или передайте из клиента
'use client';
import { useState, useEffect } from 'react';

export default function ClientComponent() {
  const [width, setWidth] = useState(0);
  useEffect(() => {
    setWidth(window.innerWidth);
  }, []);
  return <div style={{ width }} />;
}

Server Components выполняются вне браузера, где DOM API не существуют. Это не проблема конфигурации — это архитектурная особенность.

5. Асинхронные компоненты без Suspense

// ❌ Необработанный промис в Server Component
async function UserProfile({ id }) {
  const user = await fetchUser(id);
  return <div>{user.name}</div>;
}

// ✅ Оборачиваем в границу Suspense
<Suspense fallback={<Loading />}>
  <UserProfile id={userId} />
</Suspense>

React Server Components могут быть асинхронными, но без правильных границ Suspense они либо заблокируют рендеринг, либо упадут с загадочными ошибками.

Подводные камни конфигурации современного JSX

6. Несоответствие конфигурации JSX Runtime

// ❌ Старая трансформация в tsconfig.json
{
  "compilerOptions": {
    "jsx": "react"  // Требует импорта React
  }
}

// ✅ Автоматический runtime для React 17+
{
  "compilerOptions": {
    "jsx": "react-jsx"  // Импорт React не нужен
  }
}

Автоматический JSX runtime — это не просто удобство, он необходим для оптимального размера бандла и совместимости с Server Components. Неправильная конфигурация здесь вызывает скрытые сбои, которые проявляются только в продакшене.

7. Антипаттерны условного рендеринга

// ❌ Возвращает 0 вместо ничего
{count && <Counter value={count} />}

// ✅ Явное преобразование в boolean
{Boolean(count) && <Counter  value={count} />}

Когда count равен 0, JSX рендерит число 0, а не ничего. Эта ошибка особенно заметна в React Native, где текстовые узлы требуют правильных контейнеров.

Стратегии предотвращения

Правильно настройте инструменты: Настройте ESLint с eslint-plugin-react и включите эти правила:

  • react/jsx-key
  • react/jsx-no-bind
  • react/display-name

Используйте TypeScript: При правильной конфигурации JSX TypeScript ловит большинство этих ошибок на этапе компиляции. Включите режим strict и правильно настройте jsx в вашем tsconfig.json.

Понимайте свой runtime: Знайте, выполняется ли ваш компонент на сервере или клиенте. Next.js 14+ делает это явным с помощью директивы 'use client', но ментальная модель применима везде.

Заключение

Ошибки JSX в 2024 году — это не просто вопрос синтаксиса, это понимание того, где и как выполняется ваш код. Автоматический JSX runtime изменил модель трансформации. Server Components изменили модель выполнения. Конкурентные функции React изменили модель производительности.

Освойте эти основы, и вы будете писать JSX, который не просто корректен, но и оптимизирован для возможностей современного React. Лучший JSX — невидимый — он не мешает и позволяет вашим компонентам сиять.

Часто задаваемые вопросы

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

Хотя inline-функции работают функционально, они создают новые ссылки при каждом рендере, нарушая оптимизацию React.memo и потенциально вызывая ненужные повторные рендеры дочерних компонентов. Для лучшей поддерживаемости используйте useCallback для обработчиков событий, которые зависят от props или state.

Настройка react использует классическую трансформацию React.createElement, требующую импорта React в каждом файле. Настройка react-jsx использует автоматический runtime, представленный в React 17, который обрабатывает трансформацию без явного импорта React и создаёт меньшие бандлы.

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay