Синтаксис JSX: списки и события
-
JSX — это не XML, а просто сахар
JSX — это синтаксический сахар над
React.createElement()
, который позволяет писать HTML-подобный код прямо в JavaScript. И как по мне я его очень люблю, а с приходом Next.js так вообще работать с React одно удовольствие.Да:
<div>Привет, JSX</div>
— это валидный JS. Но не будем спешить радоваться: здесь есть свои подводные камни, особенно когда речь заходит о событиях и списках. Их разбирали уже много сотен раз, но я хочу поделиться своими мыслями и показать конкретные примеры.
В отличие от нативного HTML, где мы пишете
onclick
, в JSX события пишутся вcamelCase
и передаются как функции, а не строки. То есть неonClick="handleClick()"
, аonClick={handleClick}
. Обратим внимание на фигурные скобки и отсутствие кавычек — это не HTML!handleKeyUp
function handleKeyUp(e) { document.getElementById('title').innerText = e.target.value; }
Что происходит: Каждый раз, когда вы отпускаете клавишу в инпуте, функция лезет в DOM через
document.getElementById
и меняет текст элемента сid="title"
.Как применяется:
<input onKeyUp={handleKeyUp} type="text" />
Почему так делать НЕ НАДО:
В React никогда не трогайте DOM напрямую (ну, кроме крайних случаев). Это как писатьvar
вместоconst
— технически работает, но коллеги будут плакать. В реальности вы бы использовалиuseState
и обновляли состояние.Но для примера сойдёт.
handleClick
function handleClick(e) { document.getElementById('title').innerText += ' ' + e.target.textContent; }
Что происходит: При клике на кнопку к тексту заголовка добавляется пробел и текст самой кнопки (в нашем случае —: Строка).
Как применяется:
<button onClick={handleClick}>Строка</button>
Лайфхак: Обратите внимание, что
e.target.textContent
— это содержимое кнопки. Так что если мы напишем<button>Клик!</button>
, в заголовок добавится “Клик!”.
handleMouseEnter / handleMouseLeave
function handleMouseEnter(e) { e.target.classList.add('hover'); } function handleMouseLeave(e) { e.target.classList.remove('hover'); }
Что происходит: При наведении курсора на кнопку добавляется класс
hover
, при уходе — удаляется. В CSS вы бы прописали стили для.hover
, чтобы кнопка светилась, как неоновая вывеска.Как применяется:
<button onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} > Кнопка </button>
Для чего это нужно: Hover-эффекты — must-have для любого уважающего себя UI. Без них кнопки будут выглядеть как серые кирпичи.
handleMouseDown / handleMouseUp
function handleMouseDown(e) { e.target.classList.add('active'); } function handleMouseUp(e) { e.target.classList.remove('active'); }
Что происходит: При нажатии кнопки (но не отпускании!) добавляется класс
active
. Это нужно, чтобы кнопка “проваливалась” при клике — стандартный паттерн для UX.Как применяется:
<button onMouseDown={handleMouseDown} onMouseUp={handleMouseUp} > Кнопка </button>
Важно: Не путайте
onClick
иonMouseDown
!onClick
сработает при отпускании кнопки мыши, аonMouseDown
— при нажатии. Если нужно имитировать нажатие, как в реальной жизни — юзайтеonMouseDown
/onMouseUp
.
Списки в JSX: map() — ваш новый лучший друг
Ну что с событиями мы вроде разобрались теперь самое время перейти к спискам, хотя их бы стоило перенести в отдельную тему, но я все же напишу здесь.
Теперь про списки. В вашем коде три одинаковых кнопки с разными строками. Писать их вручную — как пилить воду топором. В React списки рендерятся через
map()
, а каждому элементу нужно задать ключ(key)
, чтобы React не тормозил.const emojis = ['Строка', 'Строка1', 'Строка2']; ReactDOM.render(( <div> <input onKeyUp={handleKeyUp} type="text" /> {emojis.map((emoji, index) => ( <button key={index} onMouseEnter={handleMouseEnter} onClick={handleClick} onMouseLeave={handleMouseLeave} onMouseDown={handleMouseDown} onMouseUp={handleMouseUp} > {emoji} </button> ))} </div> ), document.querySelector('#root'));
Что изменилось:
- Создали массив эмодзи.
- Проитерировались через
map()
. - Добавили
key={index}
— без этого React будет ругаться в консоли (и правильно делает).
Почему
key
обязателен?
React использует ключи для отслеживания изменений в списке. Если вы уберётеkey
, при обновлении списка React будет перерисовывать всё подряд, а не только изменённые элементы. Это как пытаться найти иголку в стоге сена без метки.
Частые ошибки новичков
-
Не трогайте DOM напрямую
Вместоdocument.getElementById
юзайтеuseState
и рефы (useRef
). Иначе вы превратите React-приложение в jQuery-халтуру. -
Всегда задавайте
key
в списках
И лучше не индекс (index
), а уникальный ID из данных. Иначе при удалении элементов всё сломается. -
События в JSX — это не HTML
Помните:onClick
, а неonclick
, и передаём функцию, а не строку. -
Не вешайте обработчики в
render
В вашем примере всё ок, но если вы напишете<button onClick={() => handleClick(e)}>
, то при каждом рендере будет создаваться новая функция. Лучше привязывайте обработчики в конструкторе или черезuseCallback
.
События и списки в JSX — базовые вещи, без которых никуда. Да, сначала кажется, что это куча магии, но как только поймёте, что JSX — это просто JS, станет намного проще.
-
Примеры преобразования JSX в JavaScript-объекты (Virtual DOM nodes), которые использует React.
Базовый элемент
// jsx <div>Hello World</div> // Компилируется в: React.createElement("div", null, "Hello World") // Результирующий объект: { type: "div", props: { children: "Hello World" }, // ... другие внутренние поля React }
Элемент с атрибутами
// jsx <img src="image.jpg" alt="Example" className="photo" /> // Компилируется в: React.createElement("img", { src: "image.jpg", alt: "Example", className: "photo" }) // Результирующий объект: { type: "img", props: { src: "image.jpg", alt: "Example", className: "photo" } }
Вложенные элементы
// jsx <div> <h1>Title</h1> <p>Content</p> </div> // Компилируется в: React.createElement( "div", null, React.createElement("h1", null, "Title"), React.createElement("p", null, "Content") ) // Результирующий объект: { type: "div", props: { children: [ { type: "h1", props: { children: "Title" } }, { type: "p", props: { children: "Content" } } ] } }
С выражением JavaScript
// jsx <div>{2 + 2}</div> // Компилируется в: React.createElement("div", null, 2 + 2) // Результирующий объект: { type: "div", props: { children: 4 } }
Компонент с пропсами
// jsx <Button color="blue" size="large"> Click me </Button> // Компилируется в: React.createElement( Button, { color: "blue", size: "large" }, "Click me" ) // Результирующий объект: { type: Button, // ссылка на функцию/класс компонента props: { color: "blue", size: "large", children: "Click me" } }
Список элементов
// jsx <ul> {items.map(item => ( <li key={item.id}>{item.name}</li> ))} </ul> // Компилируется в: React.createElement( "ul", null, items.map(item => React.createElement( "li", { key: item.id }, item.name ) ) ) // Результирующий объект: { type: "ul", props: { children: [ { type: "li", key: "1", props: { children: "First item" } }, { type: "li", key: "2", props: { children: "Second item" } } ] } }
Фрагменты
// jsx <> <Header /> <MainContent /> </> // Компилируется в: React.createElement( React.Fragment, null, React.createElement(Header, null), React.createElement(MainContent, null) ) // Результирующий объект: { type: React.Fragment, props: { children: [ { type: Header, props: {} }, { type: MainContent, props: {} } ] } }
Условный рендеринг
// jsx <div> {isLoggedIn ? <UserPanel /> : <LoginButton />} </div> // Компилируется в: React.createElement( "div", null, isLoggedIn ? React.createElement(UserPanel, null) : React.createElement(LoginButton, null) )
Как работает преобразование:
- Babel трансформирует JSX в вызовы React.createElement()
- React.createElement() возвращает объект описывающий элемент
- Эти объекты образуют Virtual DOM
- React сравнивает Virtual DOM с предыдущим состоянием и обновляет реальный DOM
Важные особенности:
- type может быть строкой (для HTML-элементов) или функцией/классом (для компонентов)
- props содержат все атрибуты, включая children
- children могут быть строкой, массивом или другим объектом
- key и ref не попадают в props, а сохраняются отдельно
-
А почему в JSX нельзя просто писать onclick как в обычном HTML? Всегда забываю про camelCase и получаю ошибки. Неужели нельзя было сделать совместимый синтаксис?
-
Это особенность JSX потому что он компилируется в JavaScript, где имена свойств чувствительны к регистру. Если бы использовали onclick, это бы не соответствовало DOM API. К тому же camelCase помогает сразу отличать встроенные события от пользовательских пропсов.
-
А что насчёт передачи аргументов в обработчики? Я часто делаю onClick={handleClick(data)}, но тогда функция вызывается сразу при рендере. Как правильно?
-
нужно обернуть в стрелочную функцию: onClick={() => handleClick(data)}. Или использовать bind: onClick={handleClick.bind(this, data)}. Первый вариант читается проще, но создаёт новую функцию при каждом рендере.
-
А есть ли разница между onClick={handleClick} и onClick={() => handleClick()}? Вроде бы работают одинаково, но первый вариант короче.
-
Разница в том, что в первом случае ты передаёшь ссылку на функцию, а во втором создаёшь новую функцию-обёртку. Для производительности лучше первый вариант, если не нужно передавать аргументы. Иначе могут быть лишние ререндеры.
© 2024 - 2025 ExLends, Inc. Все права защищены.