Первая самая важная деталь внутренностей React ,как мне кажется ,это то ,как устроены механизмы рендеринга. Далее пойдут мои рассуждения относительно работы Fiber и каких-то предшествующих ему механизмов. В чем-то могу ошибиться ,тк пока что мое понимание на самом верхнем уровне ,и я буду отталкиваться от степени своего понимания на текущий момент ,в будущем внося коррективы и переосмысливая какие-то вещи.
Итак ,Fiber - это новый механизм работы ядра реакта ,пришедший на замену старому механизму Stack Reconciler в 16 версии. По большому счету он отвечает за рендер. В данном контексте рендер обозначает не render в браузере ,а абстрактный рендер - формирование ReactElements и связанных с ними FiberNodes ,а если точнее - формирование дерева FiberTree ,сравнение новой версии дерева со старым и вычисление изменений ,которые необходимо выполнить в следующей фазе commit (в рамках которой уже будут выполнены hooks ,lifecycle hooks и произведен непосредственно рендеринг в DOM ,если речь идет о react-dom). Фазу render еще часто обозначают как reconciliation. Исходя из этого можно выделить 2 основных фазы рендеринга в React:
Ключевой аспект здесь в том ,что фаза 1 является прерываемой в отличии от фазы 2. В этом ключевое отличие от старого механизмма рендеринга. За счет этого повышается оптимизация ,становится возможна приоретизация задач ,появляется возможность отложить работу react для более приоритетных задач ,например - рендеринга браузера.
До Fiber реакту приходилось рекурсивно обходить все дерево реакт-элементов для вычисления диффа с реальным DOM ,и эта фаза была непрерываемой ,из-за чего могли быть просадки по FPS. Как мы помним ,чтобы частота была плавной 60к/c ,на один кадр рендеринга должно выделяться не более 16.6мс ,из которых как правило около 6мс резервируется на задачи браузера ,включая работу рендеринга ,а ~10 на задачи JS. Если задачи JS занимают более 10с ,вероятнее всего следующий кадр рендеринга будет пропущен. Их может быть пропущено и более ,если стек вызовов не очищается в течении длительного периода времени. Вполне возможно ситуация ,когда операции по вычислению diff могли занимать и более 32 мс ,что как правило приводит к заметным подвисаниям в браузере.
Помимо этого алгоритм обхода дерева изменился с рекурсивного на итеративный ,что в целом ускорило этот процесс и добавило новые возможности ,например ту же приостановку обхода и возобновление. У меня есть некоторые наработки по осмыслению этого процесса ,но его буду конкретно разбирать чуть позже в рамках других исследований.
Итак ,ранее в определении Fiber я упомянул дерево FiberTree. Что же оно собой представляет? По сути это структура компонентов приложения реализованная в виде дерева ,но с некоторыми нюансами. Каждый FiberNode представляет собой узел дерева ,связанный с конкретным ReactElement (это может быть элемент DOM или React-компонент классический или функциональный). FiberNode создается из состояния ReactElement и становится с ним неразрывно связан. Здесь можно сделать вывод ,что для создание ReactFiber требуется вычисленный ReactElement. Чтобы его вычислить и далее с ним работать ,необходимо вызвать render-функцию ,связанную с этим ReactElement. В случае компонента react - это вызов самой функции-компонента или вызов метода render классического компонента. В результате чего появляется понимание ,для чего React вызывает рендеры всего поддерева - чтобы вычислить текущее состояние реакт-элементов и далее связанный с ним Fiber для последующего выяснения того ,что нужно обновить и вызвать на этапе commit.
Поэтому остановимся на 2 вещах.
В первый раз React создаст целиком дерево React-элементов ,а так же целиком дерево FiberTree на его основе. Это будет сделано при вызове createRoot - render. В дальнейшем при обновлении компонентов (изменении props, state или контекстов) будут созданы соответствующее измененным реакт-элементам FiberNodes для последующего их сравнения с предыдущим состоянием. Иными словами сформировано новое дерево FiberTree. Но где хранится предыдущее состояния файберов?
Для того ,чтобы React мог найти изменение ,необходимо сохранять ссылку на старое (или правильно говоря текущее) дерево FiberTree. Оно будет называться CurrentFiberTree. В процессе рендера реакта формируется новое дерево WorkInProgressTree ,сравнение которых и называется процессом reconciliation. После завершения этого процесса CurrentFiberTree становится WorkInProgressTree. А ссылка на уже старый CurrentFiberTree удаляется.
Если заглянуть в определение типа Fiber ,то можно найти интересные свойства ,вносящие чуть больше понимания в то ,чем является FiberNode. Рассмотрим некоторые его свойства:
Полей гораздо больше ,но этого пока хватит. Ключевой момент касающийся нового алгоритма обхода дерева -заложен в свойствах return, child и sibling ,который намекает на использование односвязанного списка для прохода уровня дочерних узлов. Поле type намекает на то ,с чем связан данный FiberNode - с dom-элементом или react-компонентом. stateNode - содержит состояние ассоциированного reactElement. На основе flags вероятно react определяет ,что конкретно нужно сделать в контексте этого FiberNode.
Я постарался обобщить все упомянутые процессы ,тк под капотом просиходит гораздо больше всего. Также не ссылался на исходный код ,хотя под созданием fiberNodes ,запуском процесса сравнения и самого цикла обхода стоят конкретные функции со своими особенностями. В дальнейшем я буду детальнее их рассматривать ,но сейчас задача в общих чертах разобрать Fiber ,получить базовое представление ,понять терминологию и общую схему процессов.
После прочтения документации понял ,что допустил ошибку относительно инстансов. Инстанс элемента создается один раз и далее переиспользуется между рендерами. Выжимка из доки - "When a component updates, the instance stays the same, so that state is maintained across renders.". Правильно было говорить не об инстансе ,а о состоянии компонента. То есть происходит вычисление нового состояния и вызов render с этим новым состоянием. Render как я понимаю вернет новый ReactElement ,на основе которого будет вычислен новый связанный с ним Fiber.
Мы поможем вам ее достичь!
310 000
единомышленников
инструменты
для увлекательного достижения