Статья впервые опубликована вличный блог
предисловие
Концепция, которая была раскрыта в 2016 году, это все 9102 года, я только начал писать статью о Файбере, мне стыдно. Но сейчас хорошо, что информация о Файбере уже очень богатая, при написании статей больше справочных материалов, да и разобраться глубже проще.
Как мой любимый фреймворк, React не входит в их число. Я готов потратить много времени, чтобы хорошенько изучить его. Я обнаружил, что есть четыре вида чувств при изучении фреймворка. незнакомый синтаксис. Я чувствовал, что это какая-то ерунда. Разве это не античеловеческое; потом, когда вы ознакомились с ним, это было действительно ароматно, и дизайн был очень хорош. В это время он изменил ваш способ Я думал о программировании. Позже, после прочтения его исходного кода и понимания его дизайна, дизайн действительно хорош, и я чувствую, что могу написать его сам.
Так что яЭтот год(Да-да, всего один год) Я просто хотел выучить React полностью, поэтому открыл одинDeep In ReactСерия, некоторые новички не знают, почему при использовании API, и почему некоторые вещи спроектированы таким образом, и обсуждают с вами тайну React.
Моя идея — введение сверху вниз, сначала понять общую архитектуру Fiber, а затем подробно изучить каждый пункт, поэтому в этой статье в основном рассказывается об архитектуре Fiber.
представлять
Прежде чем вдаваться в подробности о Fiber, давайте разберемся, что такое Fiber и почему команде React потребовалось два года, чтобы реорганизовать алгоритм координации.
Основная идея React
Виртуальное DOM-дерево поддерживается в памяти, при изменении данных (setState) виртуальный DOM автоматически обновляется для получения нового дерева, затем новое и старое виртуальные DOM-дерева сравниваются, и находит измененную часть, и Получено изменение (патч), патчи добавляются в очередь, и, наконец, эти патчи пакетами обновляются в DOM..
Недостатки до React 16
Во-первых, давайте разберемся с рабочим процессом React, когда мы переходимrender()
а такжеsetState()
При рендеринге и обновлении компонентов React имеет два основных этапа:
Согласователь:официальное объяснение. React будет выполнять рекурсию сверху вниз, проходить новые данные для создания новой виртуальной модели DOM, а затем использовать алгоритм Diff для поиска элемента (исправления), который необходимо изменить, и помещать его в очередь обновлений.
Рендерер: Проходя очередь обновления, хост-среду, вызывая API, ваше реальное обновление отображает соответствующие элементы. Хост-среда, такая как DOM, Native, WebGL.
На этапе согласования из-за рекурсивного метода обхода это также называетсяStack Reconciler, главным образом, чтобы различатьFiber ReconcilerВозьми имя. Одной из особенностей этого подхода является то, что как только задача начинается,нельзя прервать, то js всегда будет занимать основной поток, а право выполнения не будет передано движку рендеринга до тех пор, пока не будет вычислено все дерево Virtual DOM, что приведет к тому, что некоторые задачи, такие как взаимодействие с пользователем и анимация, не будут обрабатываться сразу, и будет Caton, который сильно влияет на пользовательский опыт.
Как решить предыдущие недостатки
Основная проблема предыдущей проблемы заключается в том, что после выполнения задачи ее нельзя прервать, а поток js занимает основной поток, что приводит к зависанию.
Возможно, некоторые люди, которые давно контактируют с фронтендом, не особо понимают, почему js продолжает занимать основной поток и зависает, я просто популяризирую его здесь.
Что браузер должен делать в каждом кадре?
Страница рисуется кадр за кадром. Когда число кадров в секунду (FPS) достигает 60, страница становится гладкой. Когда оно меньше этого значения, пользователь чувствует себя застрявшим.
1 с — это 60 кадров, поэтому время, выделяемое каждому кадру, составляет 1000/60 ≈ 16 мс. Поэтому, когда мы пишем код, мы стараемся, чтобы нагрузка на один кадр не превышала 16 мс.
Работа браузера в одном кадре
Как видно из приведенного выше рисунка, следующие шесть шагов необходимо выполнить в одном кадре:
- обрабатывать взаимодействие с пользователем
- Разбор и выполнение JS
- Кадр запускается. Изменение размера окна, прокрутка страницы и т.д.
- rAF(requestAnimationFrame)
- макет
- рисовать
Если какой-либо из этих шести шагов занимает слишком много времени, а общее время превышает 16 мс, пользователь может увидеть зависание.
и упоминалось в предыдущем разделеэтап примиренияЕсли затраченное время слишком велико, то есть время выполнения js слишком велико, то возможно, что при взаимодействии пользователя должен отрисовываться следующий кадр, но JS все еще выполняется в текущем кадре, что будет привести к взаимодействию с пользователем. Не утруждайте себя получением обратной связи, что приводит к ощущению заикания.
решение
Разделите процесс обновления рендеринга на несколько подзадач, каждый раз выполняйте только небольшую часть и смотрите, осталось ли еще время после завершения, если есть, переходите к следующей задаче, если нет, приостанавливайте текущую задачу и сдавайте контроль времени мастеру. Потоки продолжают выполняться, когда основной поток не занят.Эта стратегия называетсяСовместное планирование, одна из распространенных стратегий планирования задач в операционной системе.
Дополнительные знания, Общие стратегии планирования задач операционной системы: алгоритм планирования в порядке очереди (FCFS), алгоритм планирования с приоритетом коротких заданий (процессов) (SJ/PF), алгоритм планирования с наивысшим приоритетом (FPF), алгоритм планирования с высоким коэффициентом отклика (HRN) ), метод ротации временных интервалов (RR), метод обратной связи с многоуровневой очередью.
Совместное планирование в основном используется для назначения задач.Когда приходит задача обновления, она не выполняет операцию сравнения немедленно, а сначала отправляет текущее обновление в очередь обновлений, а затем передает его в очередь обновлений.SchedulerДля обработки планировщик будет обрабатывать это обновление в соответствии с текущим использованием основного потока. Чтобы реализовать эту функцию, используйтеrequestIdelCallback
API. Для браузеров, которые не поддерживают этот API, React добавит полифилл.
Из вышеизложенного мы уже знаем, что браузер выполняет кадр за кадром, между двумя кадрами выполнения основной поток обычно имеет небольшое время простоя.requestIdleCallback
можно найти в этомПериод простоя (Idle Period) вызывает обратный вызов периода простоя (Idle Callback), выполнить некоторые задания.
- малоприоритетные задачи
requestIdleCallback
иметь дело с; - Высокоприоритетные задачи, такие как связанные с анимацией
requestAnimationFrame
иметь дело с; -
requestIdleCallback
Обратный вызов периода простоя может вызываться в течение нескольких периодов простоя для выполнения задач; -
requestIdleCallback
Метод обеспечивает крайний срок, то есть ограничение времени выполнения задачи, чтобы разделить задачу, избежать длительного выполнения, заблокировать рендеринг пользовательского интерфейса и вызвать пропуск кадров;
Это решение кажется действительно хорошим, но при его реализации может возникнуть несколько проблем:
- Как разбить на подзадачи?
- Насколько уместна подзадача?
- Как узнать, есть ли еще время?
- Как запланировать, какая задача должна быть выполнена, когда есть оставшееся время?
- Как насчет задач, пока не осталось времени?
Далее, вся архитектура Fiber предназначена для решения этих проблем.
Что такое клетчатка
Для решения проблем, возникших с ранее упомянутыми решениями, были предложены следующие цели:
- Приостановите работу и вернитесь позже.
- Расставляйте приоритеты для разных видов работы.
- Повторное использование ранее выполненной работы.
- Прервите работу, если она больше не нужна.
Чтобы сделать это, нам сначала нужен способ разбить задачи на блоки. В каком-то смысле это Fiber, а Fiber представляет собойрабочая единица.
Но простое разбиение на блоки не может выполнять задачи прерывания, потому что стек вызовов функций такой, каждая функция — это работа, и каждая работа вызывается.кадр стека, он будет работать до тех пор, пока стек не станет пустым и его нельзя будет прервать.
Итак, нам нужно планирование инкрементного рендеринга, затем нам нужно повторно реализовать планирование кадра стека, который может выполнять их в соответствии со своим собственным алгоритмом планирования. Кроме того, поскольку эти стеки могут управляться сами по себе, могут быть добавлены такие функции, как параллелизм или границы ошибок.
Таким образом, Fiber — это перереализованный стековый фрейм, по сути, Fiber также можно понимать каккадр виртуального стека, разбить прерываемую задачу на несколько подзадач и изменить предыдущую синхронную визуализацию на асинхронную, свободно планируя подзадачи в соответствии с их приоритетами и обновляя их в сегментах.
Таким образом, мы можем сказать, что Fiber — это структура данных (фрейм стека) или решение прерываемых вызовов задач, его характеристики:разрезание времениа такжеприостановка (супэнс).
если ты понимаешьсопрограммаВы можете подумать, что решение Fiber немного похоже на сопрограмму (разница все же очень большая), ее можно прерывать, а порядком выполнения можно управлять. Генератор в JS на самом деле является способом использования сопрограмм, но с меньшей гранулярностью, он может управлять порядком вызовов кода в функции, а также может быть прерван.
Как работает волокно
-
ReactDOM.render()
а такжеsetState
чтобы начать создавать обновления. - Добавьте созданное обновление в очередь задач и дождитесь планирования.
- Выполнять задачу, когда requestIdleCallback бездействует.
- Пройдите по оптоволоконному узлу от корневого узла и постройте дерево WokeInProgress.
- Сгенерировать список эффектов.
- Обновите DOM на основе списка эффектов.
Ниже представлена подробная схема процесса выполнения:
- Первая часть из
ReactDOM.render()
В начале метода преобразуйте полученный элемент React в узел Fiber, установите для него приоритет, создайте обновление и добавьте его в очередь обновлений.В основном эта часть предназначена для подготовки некоторых исходных данных. - Вторая часть в основном выполняет три функции:
scheduleWork
,requestWork
,performWork
, то есть трилогия организации работы, подачи заявки на работу и формальной работы.В этой части реализована новая функция асинхронного вызова React 16. Эта частьЭтап расписания, представленное ранее совместное планирование находится на этом этапе, и третья часть будет продолжать выполняться только тогда, когда решение получит исполняемый квант времени. Далее в статье будет рассказано о том, как его запланировать.Это ключевой процесс планирования в React. - Третья часть представляет собой большой цикл, проходящий через все узлы Fiber, вычисляющий всю работу по обновлению с помощью алгоритма Diff и выводящий EffectList для использования на этапе фиксации.Основой этой части является функция beginWork, которая в основномFiber Reconciler, включая этапы согласования и фиксации.
Fiber Node
Можно сказать, что узел FIber, который несет очень важную контекстную информацию, выполняет весь процесс создания и обновления и перечисляет некоторые важные поля Fiber в группах.
{
...
// 跟当前Fiber相关本地状态(比如浏览器环境就是DOM节点)
stateNode: any,
// 单链表树结构
return: Fiber | null,// 指向他在Fiber节点树中的`parent`,用来在处理完这个节点之后向上返回
child: Fiber | null,// 指向自己的第一个子节点
sibling: Fiber | null, // 指向自己的兄弟结构,兄弟节点的return指向同一个父节点
// 更新相关
pendingProps: any, // 新的变动带来的新的props
memoizedProps: any, // 上一次渲染完成之后的props
updateQueue: UpdateQueue<any> | null, // 该Fiber对应的组件产生的Update会存放在这个队列里面
memoizedState: any, // 上一次渲染的时候的state
// Scheduler 相关
expirationTime: ExpirationTime, // 代表任务在未来的哪个时间点应该被完成,不包括他的子树产生的任务
// 快速确定子树中是否有不在等待的变化
childExpirationTime: ExpirationTime,
// 在Fiber树更新的过程中,每个Fiber都会有一个跟其对应的Fiber
// 我们称他为`current <==> workInProgress`
// 在渲染完成之后他们会交换位置
alternate: Fiber | null,
// Effect 相关的
effectTag: SideEffectTag, // 用来记录Side Effect
nextEffect: Fiber | null, // 单链表用来快速查找下一个side effect
firstEffect: Fiber | null, // 子树中第一个side effect
lastEffect: Fiber | null, // 子树中最后一个side effect
....
};
Fiber Reconciler
Во второй части после завершения расписания и получения кванта времени начинается согласование.
Fiber Reconciler — это согласователь в React, а также процесс выполнения каждой задачи и обновления каждого узла после завершения планирования задачи, что соответствует третьей части выше.
Процесс согласования делится на 2 этапа:
- (Прерываемый) рендеринг/согласование приводит к изменению путем построения дерева WorkInProgress.
- Коммит (непрерываемый) применяет эти изменения DOM.
этап примирения
В каждом рабочем цикле фазы согласования одновременно обрабатывается одно волокно, и весь рабочий цикл может быть прерван/приостановлен после обработки. Слияние в конце каждого обновления узлаEffect Listдля сбора результатов задачи, после завершения сверки,корневой узелВсе эффекты, включая изменения DOM, записываются в список эффектов.Side Effect.
Фазу рендеринга можно понимать как процесс Diff, и получается Change(Effect List), и будет выполняться метод цикла объявления, объявленный следующим образом:
- [UNSAFE_]componentWillMount (устарело)
- [UNSAFE_]componentWillReceiveProps (устарело)
- getDerivedStateFromProps
- shouldComponentUpdate
- [UNSAFE_]componentWillUpdate (устарело)
- render
Поскольку этап согласования можно прервать, он будет выполняться повторно после того, как будет прерван и возобновлен, поэтому вполне вероятно, что методы жизненного цикла этапа согласования будут вызываться несколько раз, поэтому методы жизненного цикла этапа согласования нестабильны. , я думаю, именно поэтому от React отказалисьcomponentWillMount
а такжеcomponentWillReceiveProps
метод вместо статического методаgetDerivedStateFromProps
причина.
этап фиксации
Фазу COMMIT можно понимать как процесс отражения результатов DIFF в реальном DOM.
На этапе фиксации commitRoot будет основан наeffect
изeffectTag
, см. конкретный тег эффектаисходный код, выполнить соответствующие операции вставки, обновления, удаления в соответствии сtag
Другой, вызовите другой метод обновления.
На этапе фиксации выполняются следующие методы цикла объявления:
- getSnapshotBeforeUpdate
- componentDidMount
- componentDidUpdate
- componentWillUnmount
PS: Обратите внимание на разницу между reconciler, reconcile и reconciliation, reconciler - это reconciler, существительное, можно сказать, что это модуль работы React, модуль согласования, reconcile - это действие, согласуемое reconciler, глагол; и примирение — это только первый шаг процесса примирения на этапе.
Дерево волокон и дерево WorkInProgress
Когда React выполняет рендеринг в первый раз, он создает дерево элементов с помощью React.createElement, которое можно вызватьVirtual DOM Tree, поскольку необходимо записать контекстную информацию и добавить Fiber, каждый элемент будет соответствовать узлу Fiber, а структура, связывающая узлы Fiber, станетFiber Tree. Он отражает состояние приложения, используемого для визуализации пользовательского интерфейса. Это дерево часто называюттекущее дерево (текущее дерево, запись состояния текущей страницы).
В последующем процессе обновления (setState) Element будет пересоздаваться при каждом повторном рендеринге, а Fiber - нет. Fiber будет использовать данные в соответствующем Element только для обновления его необходимых свойств.
Важной особенностью Fibre Tree является структура связанного списка, которая рекурсивно проходит цикл программирования, а затем взаимодействует с API requestIdleCallback для разделения задач, прерывания и восстановления.
Как формируется структура этой ссылки?В основном это относится к этим полям предыдущего узла Fiber Node:
// 单链表树结构
{
return: Fiber | null, // 指向父节点
child: Fiber | null,// 指向自己的第一个子节点
sibling: Fiber | null,// 指向自己的兄弟结构,兄弟节点的return指向同一个父节点
}
Каждый узел Fiber имеет прямое соответствие с виртуальным домом, и все узлы Fiber связаны для формирования дерева Fiber, которое представляет собой древовидную структуру односвязного списка, как показано на следующем рисунке:
Глядя на картинку, можно ли узнать, как связаны Fiber Nodes?Fibre Tree — это такой односвязный список.
При рендеринге есть такой односвязный список, при вызовеsetState
Как Diff получает изменение, когда оно происходит?
используя вызываемыйДвойная буферизация, в это время необходимо другое дерево: WorkInProgress Tree, которое отражает будущее состояние, которое будет обновлено на экране.
После построения дерева WorkInProgress вы получаете новое дерево волокон, а затем вам нравится новое и не нравится старое (наведите текущий указатель на дерево WorkInProgress и отбросьте старое дерево волокон).
Преимущества этого:
- Возможность повторного использования внутренних объектов (файбер)
- Экономия выделения памяти, накладные расходы времени GC
- Даже если в работе произойдет ошибка, это не повлияет на данные в представлении.
Каждое волокно имеетalternate
Атрибут также указывает на волокно, которое берется первым при создании узла WorkInProgress.alternate
, если нет, создайте его.
Процесс создания дерева WorkInProgress также является процессом Diff.После завершения Diff будет создан список эффектов.Этот список эффектов является этапом, используемым для обработки побочных эффектов на заключительном этапе фиксации.
постскриптум
Я хотел написать статью, чтобы подробно объяснить волокно, но когда я написал ее, я обнаружил, что это слишком много Я хотел написать подробно, и предполагалось, что я напишу десятки тысяч слов, поэтому цель эта статья предназначена только для тех случаев, когда исходный код не задействован.Общий рабочий процесс React описан ниже.Для получения подробной информации, например, как планировать асинхронные задачи, как выполнять Diff и т. д., детали будут проанализированы один за другим с помощью исходный код в виде подразделов.
Честно говоря, я не особо доволен этой статьей, чувствую себя сверхутяжеленной, и я написал ее задолго до того, как рассказал о координации, но текста о координации стало меньше, потому что я специально хочу написать статью о координации, поэтому эта статья используется только для того, чтобы разобраться во всем процессе.
Но разобравшись со всем процессом, я обнаружил, что Расписание в принципе не отражено.Эй, я не хочу его писать.Эта статья слишком долго задерживается, пожалуйста, продолжайте следить за статьями.
Вы можете подписаться на мой гитхаб:Deep In React
некоторые проблемы
Вот несколько вопросов для размышления.
- Как расставлять приоритеты в задачах?
- Как пройти по связанному списку на этапе рендеринга процесса согласования и как построить workInProgress?
- Когда задача прервана, как возобновить?
- Как собрать EffectList?
- Как обновить для разных типов компонентов?
Ссылаться на
Я Тао Вэн, фронтендер, который любит думать. Если вы хотите узнать больше о фронтенде, обратите внимание на мой официальный аккаунт: «Фронтенд Таоюань».