Первый рендеринг в React: общее понимание
Еще одна попытка разобраться в подкапотье механизма Fiber. На примере самого первого рендера.
Статья (англ.): https://jser.dev/2023-07-14-initial-mount/#311-updatehostcomponent
1. Создание FiberRoot
Повторю еще раз на всякий. Начинается все с того, что мы создаем FiberRoot, от которого потом будет расти все дерево приложения.
На данный момент наше дерево представлено корнем FiberRoot, а также одним обычным Fiber типа HostRoot - он соответствует HTML-контейнеру приложения - тегу #app.
2. Триггер и планирование обновления
Первое обновление запускаем вручную, дальше они будут вызваться автоматически при изменении состояния компонентов, но это нам сейчас неинтересно:
Тут создается объект обновления для Fiber:HostRoot, внутри которого сохраняется элемент, который нужно отрендерить (<App />). Обновление помещается в очередь обновлений для Fiber:HostRoot (updateQueue).
На данном этапе обновление не происходит - оно только планируется на будущее.
Весь код до этого момента - синхронный.
3. Рабочий цикл планировщика
Переходим к циклу. У нас есть обновление, поэтому нужно создать новое Fiber-дерево. Буквально новое и прямо от корня - это новое дерево называется workInProgress.
Создаем новый Fiber:HostRoot - и начинаем цикл прямо с него.
На каждой итерации мы берем текущий Fiber-узел и - внимание! - создаем его child-узел. То есть с каждым шагом наше workInProgress-дерево растет.
Это делает функция
При создании child-узла для нового дерева обязательно учитывается, если у него уже есть текущая версия в current-дереве. Если есть, то старый узел будет обновлен, если еще нет, то узел будет создан заново из React-элемента.
Последовательность обхода дерева описана в предыдущем посте - сначала мы спускаемся вниз по дочерним узлам (сначала создаем дочерний узел, потом сразу же к нему переходим). Это фаза собственно «рендера» или «реконсиляции» - расчет изменений.
Как именно создается дочерний узел, зависит от типа текущего узла. Для HostRoot данные для рендера (дочерние элементы) достаются из того самого объекта обновления, который был создан и запланирован в самом начале. Для других узлов в основном берутся из пропсов (props.children).
Когда достигнут низ ветки и детей больше нет, начинаем подниматься вверх и искать первый узел, у которого есть сиблинг. При этом мы последовательно «закрываем» все узлы, которые уже были обработаны (снизу вверх) - это делает функция
На этом «обратном» пути для каждого узла создается соответствующий DOM-элемент, который пока просто сохраняется в поле
Также во время спуска и подъема проставляются разнообразные флаги, которые затем будут использоваться для обновления DOM (например, что узел нужно удалить или наоборот вставить в документ).
4. Коммит изменений в DOM
Итак, мы обошли все дерево, и теперь у нас есть полностью построенное дерево workInProgress, где для каждого узла проставлены флаги, что с ним нужно делать (например, вставить в DOM или удалить).
Теперь мы реально будем менять DOM - сначала удалять, потом вставлять и менять порядок, потом запускать эффекты.
Дерево workInProgress теперь становится деревом current.
#ссылки #fiber #подкапотом
Еще одна попытка разобраться в подкапотье механизма 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 #подкапотом
jser.dev
How does React do the initial mount internally?
Initial mount is the first render for a React app, it creates the internal Fiber Tree and also the DOM tree.
👍4