Перейти к основному содержимому
Блокчейн42ГЛАВА 42React: компоненты вместо ручного DOMAppBalanceFormOpsprops ↓2.10 мс

React: компоненты вместо ручного DOM

Прочитано0%
Квизы0/4
Тренажёрыне пройден0/1
Уровень 1 · Новичок

Зачем это нужно​

Прошлая глава закончилась измерением. Список из двухсот строк, изменилось одно число:

  • перерисовать всё — 33 миллисекунды, и пользователь теряет набранный текст;
  • поменять одну ячейку — быстро и безопасно, но таких мест в настоящем интерфейсе десятки, и каждое надо помнить.

Библиотека решает ровно эту задачу. Вы описываете, как интерфейс должен выглядеть при таких данных. Она сама сравнивает новое описание со старым и трогает только изменившееся.

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

Идея: описание вместо шагов​

Ручной подход — последовательность действий: найди ячейку, поставь новое значение; найди строку, добавь класс; удали лишние узлы.

Подход React — функция от данных к виду. Вы пишете: «при таких данных интерфейс выглядит так». Изменились данные — функция вызывается снова и возвращает новое описание. Что с этим описанием делать, решает библиотека.

Ключевая разница в том, чего вы не пишете: ни одной строки про «найти и заменить». Их не бывает вовсе.

JSX и во что он превращается​

Внутри кода пишут разметку. Выглядит странно — это не разметка:

function Balance({ amount, onWithdraw }) {
return (
<div className="card">
<b>{amount} ETH</b>
<button onClick={onWithdraw} disabled={amount === 0}>ok</button>
</div>
);
}

Прогон пропускает этот текст через настоящий сборщик и печатает результат:

── во что это превращается ──
function Balance({ amount, onWithdraw }) {
return _jsxs("div", {
className: "card",
children: [
_jsxs("b", { children: [amount, " ETH"] }),
_jsx("button", { onClick: onWithdraw, disabled: amount === 0, children: "ok" })
]
});
}
JSX — НЕ ЯЗЫК РАЗМЕТКИ, А ЗАПИСЬ ВЫЗОВОВпишем<b>{amount} ETH</b><button onClick={f} disabled={n === 0}>ok</button>сборкаполучается_jsxs("b", { children: [amount, " ETH"] })_jsx("button", { onClick: f, disabled: n === 0 })в браузер уезжают обычные вызовы функций: никакой разметки в готовом коде нетотсюда и правила записи: className вместо class, фигурные скобки для любого значения из кодарезультат вызова — обычный объект-описание, а не элемент страницы
Никакой разметки в готовом коде нет — только вызовы функций с объектами свойств

JSX — это сокращённая запись вызовов функции. Сборка превращает его в обычный код до того, как он попадёт в браузер.

Из этого сразу следуют все «странные» правила:

Нажми на часть выражения
Интересный факт

Результат вызова — обычный объект, описывающий, что должно быть на экране. Это не элемент страницы: у него нельзя спросить размеры и нельзя навесить обработчик напрямую. Элементы появятся, когда библиотека сравнит описание с тем, что уже нарисовано.

Компонент: функция, возвращающая описание​

function Balance({ amount }) {
return <b>{amount} ETH</b>;
}

Всё. Компонент — это функция, которая принимает данные и возвращает описание. Три правила:

  1. Имя с большой буквы. По нему запись отличает ваш компонент от обычного тега: <Balance /> против <b>. Со строчной буквы он будет понят как неизвестный тег.
  2. Возвращает одно описание. Несколько элементов подряд заворачивают в общего родителя или в пустую обёртку <>…</>.
  3. Ничего не меняет снаружи. Функция вызывается когда угодно и сколько угодно раз — она должна лишь считать результат по входным данным.
<div>
<Balance amount={12} />
<Balance amount={0} />
</div>

Один и тот же компонент, разные данные — разный результат. Это и есть повторное использование, ради которого всё затевалось.

Свойства: данные вниз, события вверх​

ДАННЫЕ ВНИЗ, СОБЫТИЯ ВВЕРХVaultBalanceDepositFormOpsListсвойстваобработчикипотомок получает данные свойствами и не может их менять — только позвать функцию, которую ему далипоэтому по нарисованному интерфейсу всегда видно, откуда взялось каждое число
Потомок получает данные и функции сверху; изменить их сам он не может

Свойства только на чтение. Потомок не может изменить то, что ему передали. Если ему нужно что-то поменять — родитель передаёт ему функцию, и потомок её зовёт.

function OpsList({ ops, onWithdraw }) {
return (
<ul>
{ops.map((op) => (
<li key={op.id}>
{op.who} — {op.amount} ETH
<button onClick={() => onWithdraw(op.id)}>Забрать</button>
</li>
))}
</ul>
);
}
Нажми на часть выражения

Про ключи отдельно. Ключ должен быть устойчивым идентификатором данных, а не номером по порядку. Номер меняется при вставке и удалении, и библиотека начинает считать, что изменилось содержимое строк, а не их состав, — с потерей введённого текста и неправильной подсветкой.

Отвечено вопросов: 0 из 2
Что такое JSX на самом деле?
Почему onClick={onWithdraw(id)} — ошибка?
Отвечено верно: 0 из 2

Условия в описании​

Отдельных «если» в записи нет — она умеет только выражения. Поэтому пользуются тремя приёмами:

{isLoading && <Spinner />}

{amount > 0 ? <b>{amount} ETH</b> : <span>пусто</span>}

{ops.length === 0 && <p>Операций пока нет</p>}
Нажми на часть выражения

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

Тот же список, но на React​

Прогон повторяет опыт прошлой главы: двести строк, в каждой поле ввода. Пользователь набрал текст, и в это время меняется одно число в первой строке.

узлов li в списке: 200
число в первой строке до: 1

функций-строк вызвано заново: 200 из 200
число в первой строке после: 42

тот же ли узел li : true
тот же ли узел b : true
тот же ли узел input : true
фокус остался в поле : true
текст в поле : "моя заметка"

Сравните с прошлой главой: там после перерисовки было фокус false, текст "".

Функции строк вызвались все двести. Это важно понять правильно: пересчёт описания — дешёвая операция, она происходит целиком. Дорогая операция — изменение страницы, и её библиотека делает точечно.

Ни один узел страницы не был пересоздан. Библиотека сравнила старое описание с новым, увидела, что отличается один текстовый узел, и поменяла только его.

Сколько это стоит​

перерисовка списка из 200 строк после изменения одного числа
среднее : 3.46 мс
медиана : 2.10 мс
минимум : 1.00 мс
СПИСОК 200 СТРОК, ИЗМЕНИЛОСЬ ОДНО ЧИСЛОруками, через innerHTML33.27 мсвсе узлы созданы зановофокус потерян, текст в поле стёртподсветка из кода исчезлазато никаких зависимостейReact2.10 мс (медиана)узлы li, b и input — те же самыефокус на месте, текст «моя заметка» целфункции строк вызваны заново: 200 из 200но страница изменена в одном месте«перерисовка» в React — это пересчёт описания, а не пересоздание страницыбиблиотека сравнивает новое описание со старым и трогает только то, что действительно изменилось
Два измерения одной и той же задачи: 33.27 мс с потерей состояния против 2.10 мс без потерь

Против 33,27 миллисекунды на ручную перерисовку — в пятнадцать раз быстрее. И это при том, что все двести функций пересчитались.

Вывод, который стоит запомнить: «перерисовка» в React — это не перерисовка страницы. Это пересчёт описания. Страница меняется ровно там, где описание разошлось со старым.

Важно

Из этого следует и обратное: если компонент делает при вызове что-то тяжёлое или что-то с побочным действием — запрос в сеть, запись в хранилище, — это будет происходить при каждом пересчёте. Вот почему компонент обязан быть просто вычислением. Куда девать всё остальное — тема следующей главы.

Отвечено вопросов: 0 из 2
Все 200 функций строк вызвались заново, но узлы страницы остались теми же. Как это понимать?
Зачем ключ у элементов списка и почему не номер по порядку?
Отвечено верно: 0 из 2

Типичные ошибки​

onClick={handler(id)} вместо стрелки. Функция вызывается во время описания; обработчику достаётся её результат. Если внутри было изменение состояния — получится бесконечный цикл.

Перенос строки после return без скобок. Инструкция завершается, функция возвращает пустоту, на экране ничего.

Компонент с маленькой буквы. Понимается как неизвестный тег и молча ничего не рисует.

Ключ — номер по порядку. Работает, пока список не меняется в середине.

{ops.length && <Список/>}. При пустом списке на экране появляется ноль.

Изменение свойств внутри потомка. Свойства только на чтение; менять данные может лишь тот, кто ими владеет.

class вместо className. Предупреждение в консоли, оформление не применяется.

Побочные действия прямо в теле компонента. Запрос в сеть при каждом пересчёте, а пересчёт бывает часто.

Отвечено вопросов: 0 из 2
Компонент написан с маленькой буквы: function balance(). Что произойдёт?
После return стоит перенос строки, а дальше разметка без скобок. Что вернёт функция?
Отвечено верно: 0 из 2

Слова главы​

  • Компонент — функция от данных к описанию интерфейса; имя с большой буквы.
  • Описание — обычный объект, а не элемент страницы; элементы появляются позже.
  • JSX — сокращённая запись вызовов; исчезает при сборке.
  • Свойства — данные, переданные сверху; только на чтение.
  • Обработчик из родителя — способ потомка сообщить наверх, что произошло.
  • Ключ — устойчивый идентификатор элемента списка, а не номер.
  • Согласование — сравнение нового описания со старым и точечное изменение страницы.
  • Пересчёт — вызов функций компонентов; дёшев и происходит целиком.

Тренажёр: набрать вслепую​

Цель: скорость ≥ 95 зн/мин, точность ≥ 88%

function Balance({ amount }) { return <b>{amount} ETH</b>; }

На чемпионате​

Разбейте интерфейс на компоненты до того, как писать код. Баланс, форма вклада, список операций, полоска состояния сети. Четыре файла вместо одного — и правки перестают задевать чужое.

Свойства называйте по смыслу данных, а не по виду. amount, а не text: через час станет понятно, зачем.

Ключ — идентификатор из данных. Для операций это хеш транзакции: он уникален и не меняется.

Не бойтесь «лишних» пересчётов. Оптимизация до того, как измерено, — потерянное время. Двести строк пересчитываются за две миллисекунды.

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

Куда углубиться​

  • Пустая обёртка — вернуть несколько элементов без лишнего узла на странице.
  • Свойство «дети» — передать содержимое внутрь компонента, как в обычный тег.
  • Компонент как значение — передать компонент свойством и нарисовать его в другом месте.
  • Строгий режим — почему в разработке всё вызывается дважды и что этим проверяют.
  • Инструменты разработчика для React — дерево компонентов и их свойства прямо в браузере.

Сводная проверка по всей главе: 8 вопросов, 5:00 на всё, без подсказок по ходу. Вопросы показываются по одному, ответ изменить нельзя. Если время выйдет — экзамен завершится с тем, что успел ответить. Пересдавать можно сколько угодно раз.

Куда дальше: проверенные ресурсы​

Видео по теме

ВидеоReact JS c Нуля – ПОЛНЫЙ Курс для начинающих (2025)Владилен Минин
ВидеоJSX в React: особенности синтаксиса, подводные камни, условный рендеринг и спискиАлександр Ламков — Friendly Frontend
ВидеоProps в React. Что такое пропсы, как передавать данные и зачем нужен childrenАлександр Ламков — Friendly Frontend
ВидеоКлючи (keys) в React простым языкомДмитрий Герасимов - о разработке в сфере IT просто
ВидеоУрок 1.14. Рендеринг списков в ReactWeb-garage

Комментарии

Обсуждение живёт в GitHub Discussions и грузится с чужого домена — поэтому только по нажатию.