TGViewer
ТОП - Тёма о программировани ТОП - Тёма о программировани @temaprog · 2.81K subscribers
Post #136 2.42K
Здарова, работяги!

Прошлые два поста были про сдвиг в голове: React всё меньше про «сравни деревья», всё больше про «какую работу и когда делать». Три поста на одной философии — перебор, так что ныряем под капот.

Начну с фундамента: с чем React вообще работает, когда рендерит. Звучит занудно, но пока тут чёрный ящик, советы про memo и colocation остаются заклинаниями: делай так, а почему — непонятно.

JSX — это объект-описание

<ChildVerySlow /> сам по себе ничего не рисует. Это сахар над вызовом функции, которая возвращает обычный объект:


<ChildVerySlow color="red" />
// превращается примерно в:
{ type: ChildVerySlow, props: { color: "red" }, key: null }


Чертёж: какой компонент и с какими пропсами показать. Эти объекты одноразовые — на каждом рендере создаются заново и выбрасываются. Хранить в них что-либо нельзя, да и негде.

Файбер — рабочая память React

А жить-то где-то надо. На каждый узел дерева — компонент, div, даже кусок текста — React заводит файбер: объект, который переживает рендеры.

В нём лежит всё, что должно пережить вызов функции: пропсы прошлого рендера, ссылки на родителя, ребёнка и соседа, пометки «здесь запланирована работа».

Деревьев из файберов два: текущее, по которому построен экран, и рабочее (work-in-progress из старой лекции), которое React собирает под обновление. В коммите React переключает указатель: достроенное рабочее дерево становится текущим. Старые файберы не выбрасываются — из них, переписав поля, React соберёт следующее рабочее дерево.

Стейт — связный список хуков

Локальный стейт тоже живёт в файбере. Никакой магии: на каждый вызов хука React заводит обычный объект, где лежит значение и ссылка на следующий хук. Вместе они сцеплены в связный список:


// fiber.memoizedState, упрощённо:
hook1 = { memoizedState: 'Тёма', next: hook2 }
hook2 = { memoizedState: 29, next: null }


Имён у звеньев нет. На новом рендере React идёт по списку заново: первый вызов хука получает первое звено, второй — второе. Всё сопоставление держится на порядке вызовов. Поэтому хукам нельзя жить в условиях:


function Profile({ isEditing }) {
const [name, setName] = useState('Тёма');
if (isEditing) {
const [draft, setDraft] = useState(name); // хук под условием
}
const [age, setAge] = useState(29);
}


Пока isEditing выключен, в списке те самые два звена. Включился — вызовов стало три. draft молча заберёт второе звено и получит 29 — чужой стейт age. А третий вызов упрётся в конец списка, и React упадёт с той самой «Rendered more hooks than during the previous render».

В лучшем случае рендер падает сразу, в худшем — стейт тихо съезжает в чужие звенья. «Хуки только на верхнем уровне» — не вкусовщина линтера, а прямое следствие того, как стейт лежит в файбере.

Зачем дерево распилено на файберы

До Fiber React рендерил рекурсией: зашёл в корень и спустился до самых листьев. Из середины рекурсии не выйти — стек вызовов на паузу не поставишь.

Файберы разворачивают рекурсию в плоский цикл. Вместо стека — те самые ссылки на родителя, ребёнка и соседа плюс указатель, на каком файбере остановились. Один шаг цикла — обработать один файбер, в исходниках он так и называется: performUnitOfWork. После любого шага React может отложить дерево, заняться срочным и продолжить с того же места. Управляемая срочность из прошлого поста стоит ровно на этой структуре.

В следующей части — что происходит, когда по файберам идёт рендер: две фазы, чем рендер React отличается от рендера браузера и почему ререндер дёшев, а дорог код внутри.

Резюме

— JSX-элемент — одноразовое описание { type, props, key }: создаётся на каждом рендере и выбрасывается;
— файбер — объект, который переживает рендеры: прошлые пропсы, ссылки по дереву, список хуков;
— хуки сопоставляются со звеньями списка только по порядку вызова, поэтому им нельзя жить в условиях;
— дерево обходится по одному файберу за шаг, между шагами React может прерваться — на этом стоит управляемая срочность.
  • 🔥 37
  • ❤ 8
  • 👏 4
  • 👍 1
More from @temaprog
  1. Sep 15, 2026Здарова, работяги! Кто собирается на Avito.Tech.Conf 26 сентября? Я уже рассказывал про ко…
  2. Sep 14, 2026Зачем ты пишешь? Здарова, работяги! Помню, как все только начинали активно использовать аг…
  3. Sep 4, 2026Здарова, работяги! Приезжает коробка. Внутри бейдж на Avito.Tech.Conf и часы «Ракета».😳 И…
  4. Sep 3, 2026Здарова, работяги! Потихоньку продолжаю дописывать посты из заметок. В прошлый раз — элеме…
  5. Aug 26, 2026Здарова, работяги! Совсем скоро осень, а значит, начинается сезон конференций. Про Avito.T…
  6. Aug 18, 2026Здарова, работяги! Сейчас я много думаю про AI-native команды и внедрение ИИ в разработку.…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →