Исходный код React показал 1 дизайн архитектуры и первый рендеринг экрана

React.js
Исходный код React показал 1 дизайн архитектуры и первый рендеринг экрана
Добро пожаловать на подпискуДемистификация технологии ReactСсылка на код React 16.13.1.

Чем он отличается от других руководств по React?

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

  1. Короткие и лаконичные статьи, такие как «xx строк кода помогут вам реализовать Mini React» и «xx строк кода для реализации хука React». Если вы просто хотите потратить немного времени на понимание того, как работает React, я вам рекомендуюэта статья,очень волнующе.

  2. «Принципы React Fibre», «Принципы React expireTime» — это выдержки из объяснений исходного кода React. Если вы хотите изучить исходный код React, когда не знаетеFiberчто это, не знаюexpirationTimeКогда дело доходит до смысла React, такая статья вызовет у людей ощущение: «Я понимаю код, который вы объяснили, но что этот код делает».

Я собираюсь написать эту серию статей иСоответствующий складсуществует для решения этой проблемы.

Короче говоря, эта серия статей объяснит, почему React делает то, что он делает, и как он это делает в целом, но не будет большого куска кода, рассказывающего вам, как это сделать.

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

В то же время, чтобы не нагромождать много функций, объем кода слишком велик, чтобы повлиять на ваше понимание реализации той или иной функции, я набрал функцию для реализации каждой функции на складеgit tag.

Как использовать вспомогательный склад?

Если React — клубок пряжи, то его нить должна быть
RectDOM.render(<App/>, document.getElementById('app'));
В этой ветке я разобрал работу, которую React будет выполнять для первого рендеринга экрана, извлек их из кода React и добавил много комментариев, этоv1 версия Реакта.

Нет ни состояния, ни хуков, ни функциональных компонентов, ни компонентов классов, могут быть отрисованы только первые элементы экрана, но все структуры каталогов, имена файлов и методы такие же, как у React, и фрагменты кода точно такие же (потому что они копируются при отладке).

Если вы хотите прочитать исходный код React, но вас обескураживает огромное количество кода в React, я считаю, что этот проект подходит вам для начала.

Каждая статья из этой серииСоответствующий складучебные заметки. Если вы хотите учиться у меня, вы можете найти тег git соответствующей версии, клонировать его локально и установить зависимости.
npm start
Пример текущей версии будет открыт и использован со статьей + отладка. В то же время создайте приложение React с помощью create-react-app и запустите тот же пример кода в качестве элемента управления. Вы обнаружите, что процесс рендеринга нашего проекта такой же, как и в React.

Это первая статья в этой серии, соответствующая git tag v1, обед начинается~

schedule + render + commit = React

Мы знаем, что React — это декларативная UI-библиотека, мы объявляем UI в виде компонентов, а React выведет для нас DOM и отрендерит его на страницу.


В React объявления пользовательского интерфейса выполняются с помощью метода, называемогоJSXсинтаксический сахар для реализации. JSX переводится Babel во время компиляции вReact.createElementметод.

То, что мы получаем во время выполнения, на самом деле

Результат вызова метода React.createElement, т.е.
Объект, описывающий структуру компонента.

// 输入JSX
const a = <div>Hello</div>

// 在编译时,被babel编译为React.createElement函数
const a = React.createElement('div', null, 'Hello');

// 在运行时,执行函数,返回描述组件结构的对象
const a = {
  ?typeof: Symbol(react.element),
  "type": "div",
  "key": null,
  "props": {
    "children": "Hello"
  }
}

Мы видим, что объекты, описывающие структуру компонентов во время выполнения, далеки от того DOM, который рендерится на странице.Чтобы рендерить DOM на страницу, внутри React должно быть два модуля:

  1. Отвечает за синтаксический анализ объектов JSX и определение того, какие объекты JSX необходимо окончательно преобразовать в узлы DOM.
  2. Визуализируйте элементы DOM, которые необходимо отобразить на странице.

В React мы называем работу модуля 1render, назовем работу модуля 2commit.

Почему он называется таким именем, подумайте о методе рендеринга ClassComponent, который вы написали вrenderОдна из вещей, которую делает стадия, — это выполнение метода рендеринга.

Что касается фиксации, вы можете подумать о git commit . На самом деле рабочий процесс React очень похож на многоветвевую разработку Git.

Итак, обновим нашу схему:


Краткий анализ этапа расписания

Пока что мы вкратце представили рендеринг и коммит.Эти два этапа мы уже можем реализовать в дополнение к асинхронному режиму (Concurrent) вне большей части функциональности React.

Однако представьте себе следующий сценарий:

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


Здесь есть 2 изменения состояния:
  1. Символы, введенные пользователем в поле ввода, меняются
  2. Содержимое раскрывающегося списка, показывающее изменения результатов сопоставления в реальном времени
Когда эти два изменения состояния запускаются одновременно, мы обычно ожидаем, что входное содержимое поля ввода не может застрять, и допустима задержка обновления раскрывающегося окна, отображаемого в режиме реального времени. результат.Другими словами, если приоритет 1 может быть выше, чем 2, взаимодействие с пользователем должно быть лучше.

Даже из крайних соображений мы уже запустили 2, а пользователь снова запустил 1 в процессе расчета DOM-узлов, которые необходимо изменить в 2. В это время, если мы можем поставить 2 на удержание и дать приоритет 1, этот опыт соответствует ожиданиям.

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

Чтобы достичь этого, мы знали, что нам нужно добавитьscheduleсцена:

  1. scheduleэтап, когда состояние триггера меняется,scheduleЭтап определяет приоритет запущенных обновлений и сообщает этапу рендеринга, какое обновление должно быть обработано следующим.
  2. renderэтап, полученныйscheduleУведомление этапа, которое обрабатывает обновление соответствующего JSX и решает, какие объекты JSX необходимо окончательно визуализировать.
  3. commitсцена, воляrenderСодержимое, которое необходимо отобразить на этапе, отображается на странице.


Краткий анализ фазы фиксации

Основываясь на нашем текущем дизайне,commitэтап ответственныйВизуализируйте элементы DOM, которые необходимо отобразить на странице.

Но амбиции React никогда не ограничивались веб-сферой.renderПосле того, как этап определяет, какой JSX нужно отрисовать, мы соответствуем разнымcommit, вы можете добиться рендеринга на разных платформах.


  • ReactDOMОтрисовка в браузере
  • ReactNativeРендеринг собственных компонентов приложения
  • ReactTestРендеринг чистых Js-объектов для тестирования
  • ReactArtРендеринг в Canvas, SVG или VML (IE8)

Наименьшая единица рендера — Fiber

Чтобы достичь наших трех этапов, есть три небольшие проблемы:
  1. из-заrenderРезультаты, созданные этапом, могут соответствовать нескольким платформам.commit,ЭтоrenderЭтапы производят результаты, которые не могут зависеть от платформы. еслиrenderВсе узлы, сгенерированные на этапе, являются узлами DOM. Очевидно, что эти узлы нельзя использовать в нативной среде.commitиз. Поэтому нам нужна независимая от платформы структура узла.
  2. Вводимый нами JSX — это объект, описывающий структуру компонента, но он не может описать, какой узел обновляется, а какой удаляется, поэтому нам нужна структура, которая может описывать поведение узла.
  3. говоря оscheduleфаза, нам нужен низкоприоритетныйscheduleможет быть прекращено, чтобы начать заново с более высоким приоритетомscheduleиз. ТакscheduleДетализация узла должна быть достаточно тонкой, чтобы у нас был полный контроль над завершением узла.scheduleположение и очистить узелscheduleПолученные результаты начинаются заново.
Чтобы решить эти три проблемы, React предлагает метод под названиемFiberструктура, как показано ниже:

Когда мы пытаемся отобразить , вrenderЭтап генерирует структуру волокна справа.Полная структура Fiber здесь.

  • Типы узлов, которые можно сохранить в Fiber, напримерУзел App — это узел функционального компонента, узел div — собственный узел DOM, а узел I am — текстовый узел.
  • Может сохранять информацию об узле (например, состояние, реквизит).
  • Значение, соответствующее узлу, можно сохранить (например, узел App соответствует функции App, а узел div соответствует элементу div DOME). Такая структура также объясняет, почему функциональные компоненты проходятHooksСостояние можно сохранить. Потому что состояние сохраняется не на функции, а на узле Fiber, соответствующем функциональному компоненту.
  • Может сохранять поведение узла (обновление/удаление/вставка), которое будет описано позже

В React наши компоненты будут формировать дерево компонентов.Так же и со структурой Fiber нам нужно связать их вместе, чтобы сформировать дерево Fiber. Добавляем в Fiber следующие поля:

  • child: указывает на первое дочернее волокно
  • sibling: родственный узел, указывающий вправо
В то же время, поскольку Fiber проходит слой за слоем, при переходе к узлу div Fiber в графе мы уже знаем, что его родительским узлом является узел App Fiber, в это время мы можем присвоить div Fiber.return = App Fiber, то есть используйте return, чтобы указать на себя родительский узел .


Дети, а у вас много в это время? ? ?

Почему это поле называется return, а не parent, от основной команды ReactAndrew ClarkОбъяснение: Можно понять, что точки возврата к волокну возвращаются после обработки текущего волокна.Когда обрабатывается дочернее волокно, оно возвращается к своему родительскому волокну. Отлично

Итак, наша полная структура волокна выглядит так:

ты сможешьэта статьяПосмотрите, как команда React изначально разработала архитектуру Fiber.

Общий процесс рендеринга и фиксации

Теперь, когда у нас есть тип узла (Fiber), описывающий компонент, мы можем начать рендеринг выше сгиба.

Следует отметить, что в связи с внедрениемReactDOM.renderПолученный в результате рендеринг в верхней части страницы не требует других обновлений с более высоким приоритетом.Итак, для рендеринга верхней части сгиба мы просматриваемscheduleсцена.

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

Здесь мы беремДемо версии V1 проектаНапример:

когда мы впервые вошлиrenderэтап, переходим в JSX:

весьrenderСтадия должна делать 2 вещи:

  1. Перейдите JSX вниз, сгенерируйте соответствующий Fiber для дочерних узлов JSX каждого узла JSX и назначьте значения
effectTagполе указывает текущийFiberПобочные эффекты, которые необходимо выполнить, наиболее распространенные побочные эффекты:
  • Размещение вставляет узел DOM
  • Обновление обновляет узел DOM
  • Удаление удаляет узел DOM
Конечно, рендеринг в верхней части страницы включает толькоPlacement. (все эффектыТегпосмотреть здесь)

PS: Учащимся здесь может быть интересно, почему этот шаг «создает соответствующее волокно для дочерних узлов каждого узла» вместо «создает соответствующее волокно для текущего узла»? Запомните эту строку кода:

Код, который выполняет эту строку инициализации, сначала создает корневой узел Fiber, поэтому при создании волокна от корневого волокна мы всегда создаем волокна для дочерних узлов. Это первое, что нужно сделать.

2. Создайте соответствующий узел DOM для каждого волокна и сохраните его в Fiber.stateNode.

После выполнения этих двух действий мы входимcommitстадии, когда мы знаем

  1. Какие волокна должны выполнять какие действия (известно Fiber.effectTag)
  2. Волокна, которые выполняют эти операции, и соответствующие им узлы DOM (известные как Fiber.stateNode)

С этой информацией,CommitСцене нужно только пройти через всеPlacementФайбер побочного эффекта поочередно выполняет операцию вставки DOM для завершения рендеринга первого экрана.

Это первый рендер экранаrender+commitвесь процесс. Такой остроумный, как вы, неужели нет никакого давления, чтобы понять это?

Глубоко в стадии рендеринга

мы только что разговаривалиrenderСтадии будут делать 2 вещи (2 функции, которые будут вызываться), теперь давайте дадим им имя:

beginWork

Перейдите JSX вниз, сгенерируйте соответствующий Fiber для дочерних узлов JSX каждого узла JSX и установите effectTag

мы зовем егоbeginWork, то есть для каждого узлаrenderНачальная точка фазы, над которой нужно начать работу.

completeWork

Создайте соответствующие узлы DOM для каждого волокна

мы зовем егоcompleteWork, то есть для каждого узлаrenderСтадия завершает окончание работы.

мы проходимworkInProgressЭта глобальная переменная представляет текущийrenderФайбер, который обрабатывается стадией, когда инициализируется рендеринг верхней части сгиба, workInProgress === корневой Файбер.

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

performUnitOfWorkполучать по одномуFiber,передачаbeginWorkилиCompleteWork, после обработки волокна вернитесь к следующему волокну для обработки.


когдаperformUnitOfWorkвернутьnull, это означает, что фаза рендеринга всех узлов завершена.

Хотя весь процесс выглядит громоздким, он делает две вещи:

  1. Используйте обход в глубину для генерации подволокон сверху вниз и продолжайте обход до подволокон после генерации (код)
  2. Когда в обходе нет дочернего волокна, начните обход снизу вверх и создайте соответствующий узел DOM для каждого волокна, созданного на шаге 1 (код)

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

Оптимизация этапа рендеринга

Пока наша очень близка к React, просто еще две оптимизации — это просто React’s Act.

effectList

В нашем дизайне,commitСцена будет перемещаться, чтобы найти всеeffectTagВолоконный узел. Этот обход может занять много времени, если дерево волокон большое.

Но по фактуrenderмы уже знаем, для каких волокон будет установлен Fiber.effectTag, поэтому мы можем установить Fiber.effectTag вrenderОтметьте их заранее и организуйте в связанный список.

Предполагая, что Fiber, отмеченный красным на рисунке, представляет что Fiber имеет тег effectTag в этой отправке, мы используем указатель связанного списка, чтобы связать их вместе, чтобы сформировать односвязный список.effectList.

с автором ReduxDan AbramovВ словах,effectListПо сравнению с деревом Fiber это как пасхальное яйцо на рождественской елке.



имеютeffectList,commitСцене нужно только пройти по этому связанному списку, чтобы узнать всеeffectTagВолокно тоже. Эта часть кода находится вВ функции CompleteUnitOfWork.

Что особенного в рендеринге над сгибом

Согласно нашей архитектуре, мы присваиваем значения Fibers, которые необходимо вставить в DOM.

fiber.effectTag = Placement;

Это не проблема для инкрементного обновления, но слишком неэффективно для рендеринга первого экрана, ведь для рендеринга первого экрана все DOM-узлы, соответствующие узлам Fiber, должны быть отрисованы на странице.

Должны ли мы назначать effectTag = Placement всем волокнам;commitЭтапы снова и снова выполняют операции вставки DOM, чтобы сгенерировать целое дерево DOM? Для рендеринга выше сгиба нам нужно немного поработать.

когда мыrenderсценическое исполнениеcompleteWorkПри создании узла DOM, соответствующий волокну, мы пересекаем все дочерние узлы узла волокна и вставьте узел DOM на узел ребенка в созданный узел DOM.

(субволокноcompleteWorkОн будет выполнен перед родительским волокном, поэтому, когда родительское волокно выполняется, дочернее волокно должно иметь соответствующий узел DOM).см. код здесь

Таким образом, когда мы переходим к корневому узлу Fiber, у нас уже есть построенное внеэкранное дерево DOM, в настоящее время нам нужно только присвоить значениякорневой узелeffectTagможет быть вcommitЭтап монтирует сразу все дерево DOM класса.

// 仅赋值根fiber一个节点effectTag
RootFiber.effectTag = Placement; 


что происходит перед фазой рендеринга

На данный момент мы близки к реализации первого рендеринга экрана React, и остается еще один последний шаг, который должен начаться с
назначить

// 赋值根fiber
workInProgress = Rootfiber;

Что произошло между ними?

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

Чтобы понять это, нам нужно знать, чтобы исключить связанный с SSR метод, который может инициировать рендеринг сборки React?
  1. ReactDOM.render
  2. this.setState
  3. tihs.forceUpdate
  4. useReducer hook
  5. Хук useState (PS: useState на самом деле является специальным useReducer)
Поскольку существует так много способов запуска рендеринга, нам нужен единый механизм, чтобы сигнализировать о необходимости обновления компонента. В React этот механизм называется обновлением.см. код здесь. Теперь мы можем просто сосредоточиться на следующих параметрах обновления
{
  // UpdateState | ReplaceState | ForceUpdate | CaptureUpdate
  tag: UpdateState,
  // 更新的state
  payload: null,
  // 指向当前Fiber的下一个update
  next: null
}
Это можно понять так:

Вызов this.setState React ClassComponent сгенерирует обновление, update.payload — это состояние, которое необходимо обновить, которое выполняется на волокне, соответствующем ClassComponent.beginWorkПри обновлении состояния будут обрабатываться изменения состояния компонента, вызванные обновлением состояния.Конечно, мы не реализовали это в версии V1.

для звонкаReactDOM.renderПри инициализации корневого волокна будет сгенерировано обновление, а update.payload — это соответствующий JSX, который необходимо отрендерить (см. код здесь) в корне волокнаbeginWorkзапустит процесс рендеринга, упомянутый в этой статье.

последний из последних

До сих пор мы проходим через процесс первого экрана React. Если вы видите это, дайте себе раунд аплодисментов.

Пространство ограничено, мы говорим о многих макровещах, чтобы понять детали, все еще нужно много отладочного кода, чтобы наша демонстрация прошла несколько раз.

Вот порекомендую вам отличную статью по принципу React Эффект от еды с этой статьей очень хороший.