Синтаксис 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. Все права защищены.