React Junior
207 subscribers
37 photos
462 links
Изучение React с нуля
加入频道
Первый рендеринг в React: общее понимание

Еще одна попытка разобраться в подкапотье механизма Fiber. На примере самого первого рендера.

Статья (англ.): https://jser.dev/2023-07-14-initial-mount/#311-updatehostcomponent

1. Создание FiberRoot

Повторю еще раз на всякий. Начинается все с того, что мы создаем FiberRoot, от которого потом будет расти все дерево приложения.


import { createRoot } from 'react-dom/client'
const $appContainer = document.getElementById('app’)
const root = createRoot($appContainer)


На данный момент наше дерево представлено корнем FiberRoot, а также одним обычным Fiber типа HostRoot - он соответствует HTML-контейнеру приложения - тегу #app.

2. Триггер и планирование обновления

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


root.render(‘<App />’)


Тут создается объект обновления для Fiber:HostRoot, внутри которого сохраняется элемент, который нужно отрендерить (<App />). Обновление помещается в очередь обновлений для Fiber:HostRoot (updateQueue).

На данном этапе обновление не происходит - оно только планируется на будущее.

Весь код до этого момента - синхронный.

3. Рабочий цикл планировщика

Переходим к циклу. У нас есть обновление, поэтому нужно создать новое Fiber-дерево. Буквально новое и прямо от корня - это новое дерево называется workInProgress.

Создаем новый Fiber:HostRoot - и начинаем цикл прямо с него.

На каждой итерации мы берем текущий Fiber-узел и - внимание! - создаем его child-узел. То есть с каждым шагом наше workInProgress-дерево растет.

Это делает функция beginWork - создает и возвращает дочерний узел, который станет текущим в следующей итерации цикла.

При создании child-узла для нового дерева обязательно учитывается, если у него уже есть текущая версия в current-дереве. Если есть, то старый узел будет обновлен, если еще нет, то узел будет создан заново из React-элемента.

Последовательность обхода дерева описана в предыдущем посте - сначала мы спускаемся вниз по дочерним узлам (сначала создаем дочерний узел, потом сразу же к нему переходим). Это фаза собственно «рендера» или «реконсиляции» - расчет изменений.

Как именно создается дочерний узел, зависит от типа текущего узла. Для HostRoot данные для рендера (дочерние элементы) достаются из того самого объекта обновления, который был создан и запланирован в самом начале. Для других узлов в основном берутся из пропсов (props.children).

Когда достигнут низ ветки и детей больше нет, начинаем подниматься вверх и искать первый узел, у которого есть сиблинг. При этом мы последовательно «закрываем» все узлы, которые уже были обработаны (снизу вверх) - это делает функция completeUnitOfWork.

На этом «обратном» пути для каждого узла создается соответствующий DOM-элемент, который пока просто сохраняется в поле stateNode, а потом будет вставлен в реальный DOM.

Также во время спуска и подъема проставляются разнообразные флаги, которые затем будут использоваться для обновления DOM (например, что узел нужно удалить или наоборот вставить в документ).

4. Коммит изменений в DOM

Итак, мы обошли все дерево, и теперь у нас есть полностью построенное дерево workInProgress, где для каждого узла проставлены флаги, что с ним нужно делать (например, вставить в DOM или удалить).

Теперь мы реально будем менять DOM - сначала удалять, потом вставлять и менять порядок, потом запускать эффекты.

Дерево workInProgress теперь становится деревом current.

#ссылки #fiber #подкапотом
👍4