Добро пожаловать на подпискуДемистификация технологии ReactСсылка на код React 16.13.1.
Чем он отличается от других руководств по React?
Предполагая, что React - это ваша ежедневная среда разработки, в повседневной разработке у вас есть идея изучить исходный код React, После поиска в Интернете вы обнаружите, что эти учебные пособия можно разделить на две категории. :
Короткие и лаконичные статьи, такие как «xx строк кода помогут вам реализовать Mini React» и «xx строк кода для реализации хука React». Если вы просто хотите потратить немного времени на понимание того, как работает React, я вам рекомендуюэта статья,очень волнующе.
«Принципы React Fibre», «Принципы React expireTime» — это выдержки из объяснений исходного кода React. Если вы хотите изучить исходный код React, когда не знаете
Fiber
что это, не знаюexpirationTime
Когда дело доходит до смысла React, такая статья вызовет у людей ощущение: «Я понимаю код, который вы объяснили, но что этот код делает».
Я собираюсь написать эту серию статей иСоответствующий складсуществует для решения этой проблемы.
Короче говоря, эта серия статей объяснит, почему React делает то, что он делает, и как он это делает в целом, но не будет большого куска кода, рассказывающего вам, как это сделать.
Когда вы прочтете статью и узнаете, что мы будем делать, давайте посмотрим на конкретную реализацию кода на складе.
В то же время, чтобы не нагромождать много функций, объем кода слишком велик, чтобы повлиять на ваше понимание реализации той или иной функции, я набрал функцию для реализации каждой функции на складеgit tag
.
Как использовать вспомогательный склад?
RectDOM.render(<App/>, document.getElementById('app'));
Нет ни состояния, ни хуков, ни функциональных компонентов, ни компонентов классов, могут быть отрисованы только первые элементы экрана, но все структуры каталогов, имена файлов и методы такие же, как у React, и фрагменты кода точно такие же (потому что они копируются при отладке).
Если вы хотите прочитать исходный код React, но вас обескураживает огромное количество кода в React, я считаю, что этот проект подходит вам для начала.
npm start
Это первая статья в этой серии, соответствующая git tag v1, обед начинается~
schedule + render + commit = React
Мы знаем, что React — это декларативная UI-библиотека, мы объявляем UI в виде компонентов, а React выведет для нас DOM и отрендерит его на страницу.
В React объявления пользовательского интерфейса выполняются с помощью метода, называемогоJSXсинтаксический сахар для реализации. JSX переводится Babel во время компиляции в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 должно быть два модуля:
- Отвечает за синтаксический анализ объектов JSX и определение того, какие объекты JSX необходимо окончательно преобразовать в узлы DOM.
- Визуализируйте элементы DOM, которые необходимо отобразить на странице.
В React мы называем работу модуля 1render, назовем работу модуля 2commit.
Почему он называется таким именем, подумайте о методе рендеринга ClassComponent, который вы написали вrenderОдна из вещей, которую делает стадия, — это выполнение метода рендеринга.
Что касается фиксации, вы можете подумать о git commit . На самом деле рабочий процесс React очень похож на многоветвевую разработку Git.
Итак, обновим нашу схему:
Краткий анализ этапа расписания
Пока что мы вкратце представили рендеринг и коммит.Эти два этапа мы уже можем реализовать в дополнение к асинхронному режиму (Concurrent) вне большей части функциональности React.
Однако представьте себе следующий сценарий:
- Символы, введенные пользователем в поле ввода, меняются
- Содержимое раскрывающегося списка, показывающее изменения результатов сопоставления в реальном времени
Даже из крайних соображений мы уже запустили 2, а пользователь снова запустил 1 в процессе расчета DOM-узлов, которые необходимо изменить в 2. В это время, если мы можем поставить 2 на удержание и дать приоритет 1, этот опыт соответствует ожиданиям.
Поэтому нам нужен механизм для обработки приоритета обновления и определения того, какие изменения состояния приносят обновления, которые должны выполняться в первую очередь.
Чтобы достичь этого, мы знали, что нам нужно добавитьscheduleсцена:
- scheduleэтап, когда состояние триггера меняется,scheduleЭтап определяет приоритет запущенных обновлений и сообщает этапу рендеринга, какое обновление должно быть обработано следующим.
- renderэтап, полученныйscheduleУведомление этапа, которое обрабатывает обновление соответствующего JSX и решает, какие объекты JSX необходимо окончательно визуализировать.
- commitсцена, воляrenderСодержимое, которое необходимо отобразить на этапе, отображается на странице.
Краткий анализ фазы фиксации
Основываясь на нашем текущем дизайне,commitэтап ответственныйВизуализируйте элементы DOM, которые необходимо отобразить на странице.
Но амбиции React никогда не ограничивались веб-сферой.renderПосле того, как этап определяет, какой JSX нужно отрисовать, мы соответствуем разнымcommit, вы можете добиться рендеринга на разных платформах.
- ReactDOMОтрисовка в браузере
- ReactNativeРендеринг собственных компонентов приложения
- ReactTestРендеринг чистых Js-объектов для тестирования
- ReactArtРендеринг в Canvas, SVG или VML (IE8)
Наименьшая единица рендера — Fiber
- из-заrenderРезультаты, созданные этапом, могут соответствовать нескольким платформам.commit,ЭтоrenderЭтапы производят результаты, которые не могут зависеть от платформы. еслиrenderВсе узлы, сгенерированные на этапе, являются узлами DOM. Очевидно, что эти узлы нельзя использовать в нативной среде.commitиз. Поэтому нам нужна независимая от платформы структура узла.
- Вводимый нами JSX — это объект, описывающий структуру компонента, но он не может описать, какой узел обновляется, а какой удаляется, поэтому нам нужна структура, которая может описывать поведение узла.
- говоря оscheduleфаза, нам нужен низкоприоритетныйscheduleможет быть прекращено, чтобы начать заново с более высоким приоритетомscheduleиз. ТакscheduleДетализация узла должна быть достаточно тонкой, чтобы у нас был полный контроль над завершением узла.scheduleположение и очистить узелscheduleПолученные результаты начинаются заново.
Когда мы пытаемся отобразить
- Типы узлов, которые можно сохранить в Fiber, напримерУзел App — это узел функционального компонента, узел div — собственный узел DOM, а узел I am — текстовый узел.
- Может сохранять информацию об узле (например, состояние, реквизит).
- Значение, соответствующее узлу, можно сохранить (например, узел App соответствует функции App, а узел div соответствует элементу div DOME). Такая структура также объясняет, почему функциональные компоненты проходятHooksСостояние можно сохранить. Потому что состояние сохраняется не на функции, а на узле Fiber, соответствующем функциональному компоненту.
- Может сохранять поведение узла (обновление/удаление/вставка), которое будет описано позже
В React наши компоненты будут формировать дерево компонентов.Так же и со структурой Fiber нам нужно связать их вместе, чтобы сформировать дерево Fiber. Добавляем в Fiber следующие поля:
- child: указывает на первое дочернее волокно
- sibling: родственный узел, указывающий вправо
Дети, а у вас много в это время? ? ?
Почему это поле называется return, а не parent, от основной команды ReactAndrew ClarkОбъяснение: Можно понять, что точки возврата к волокну возвращаются после обработки текущего волокна.Когда обрабатывается дочернее волокно, оно возвращается к своему родительскому волокну. Отлично
Итак, наша полная структура волокна выглядит так:
Общий процесс рендеринга и фиксации
Теперь, когда у нас есть тип узла (Fiber), описывающий компонент, мы можем начать рендеринг выше сгиба.
Следует отметить, что в связи с внедрениемReactDOM.renderПолученный в результате рендеринг в верхней части страницы не требует других обновлений с более высоким приоритетом.Итак, для рендеринга верхней части сгиба мы просматриваемscheduleсцена.
Например, только что представилscheduleПример поля ввода адреса на этапе, первый экран отображает поле ввода, а обновление с более высоким приоритетом генерируется путем ввода текста в поле ввода позже.
когда мы впервые вошлиrenderэтап, переходим в JSX:
весьrenderСтадия должна делать 2 вещи:
- Перейдите JSX вниз, сгенерируйте соответствующий Fiber для дочерних узлов JSX каждого узла JSX и назначьте значения
- Размещение вставляет узел DOM
- Обновление обновляет узел DOM
- Удаление удаляет узел DOM
PS: Учащимся здесь может быть интересно, почему этот шаг «создает соответствующее волокно для дочерних узлов каждого узла» вместо «создает соответствующее волокно для текущего узла»? Запомните эту строку кода:
2. Создайте соответствующий узел DOM для каждого волокна и сохраните его в Fiber.stateNode.
После выполнения этих двух действий мы входимcommitстадии, когда мы знаем
- Какие волокна должны выполнять какие действия (известно Fiber.effectTag)
- Волокна, которые выполняют эти операции, и соответствующие им узлы 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, это означает, что фаза рендеринга всех узлов завершена.
Хотя весь процесс выглядит громоздким, он делает две вещи:
- Используйте обход в глубину для генерации подволокон сверху вниз и продолжайте обход до подволокон после генерации (код)
- Когда в обходе нет дочернего волокна, начните обход снизу вверх и создайте соответствующий узел DOM для каждого волокна, созданного на шаге 1 (код)
В этом процессе, если встречается родственный узел, шаг 1 повторяется до тех пор, пока он, наконец, не вернется к корневому волокну, чтобы завершить создание и обход всего дерева.
Оптимизация этапа рендеринга
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;
что происходит перед фазой рендеринга
// 赋值根fiber
workInProgress = Rootfiber;
Что произошло между ними?
Просмотрите небольшой класс: workInProgress относится к волокну, обрабатываемому на текущем этапе рендеринга, ReactDOM.render создаст RootFiber, который будет назначен workInProgress.
- ReactDOM.render
- this.setState
- tihs.forceUpdate
- useReducer hook
- Хук useState (PS: useState на самом деле является специальным useReducer)
{
// 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 Эффект от еды с этой статьей очень хороший.