React Fiber

внешний интерфейс алгоритм JavaScript React.js

fiber

Когда реакция выполняет рендеринг компонента, весь процесс от setState до рендеринга является синхронным («одноразовым»). Если компоненты, которые необходимо отобразить, относительно велики, выполнение js будет занимать основной поток в течение длительного времени, что приведет к плохой отзывчивости страницы, что сделает реакцию менее эффективной в таких приложениях, как анимация и жесты.

Чтобы решить эту проблему, после двух лет работы команда React переписала основной алгоритм React.reconciliation. И выпустил эту новую функцию в версии v16. Чтобы различать предыдущий и последующий согласователи, предыдущий согласователь обычно называют согласователем стека, а переписанный — волоконным согласователем, или сокращенно Fiber.

Катон причина

Рабочий процесс согласователя стека очень похож на процесс вызова функции. Дочерний компонент в родительском компоненте можно сравнить с рекурсией функции (поэтому он называется согласователем стека). Сразу после setState react запускает процесс согласования, переходя от родительского узла (Virtual DOM), чтобы найти разницу. После завершения всего обхода Virtual DOM согласовщик может дать информацию о том, что реальную DOM необходимо изменить в данный момент, и передать ее средству визуализации для рендеринга, после чего обновленное содержимое будет отображаться на экране. Для особо огромного дерева vDOM процесс сверки будет очень долгим (x00ms), в этот период основной поток занят js, поэтому любое взаимодействие, вёрстка и отрисовка остановятся, создав у пользователя ощущение, что страница застрял. .

Scheduler

Планирование (scheduling) — это процесс согласования волокон, в основном для того, чтобы решить, что и когда следует делать. Процесс 👆 показывает, что в согласовщике стека согласование является «однократным». Для функций это не проблема, потому что нам нужны только результаты выполнения функций, но для пользовательского интерфейса нам также необходимо учитывать следующие проблемы:

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

Так что в идеале процесс согласования должен быть таким, как показано на рисунке ниже, выполняя только небольшую задачу за раз.После ее завершения вы можете «вздохнуть» и вернуться к основному потоку, чтобы посмотреть, нет ли чего-то выше приоритет.Задачу необходимо обработать.Если она есть, то задача с более высоким приоритетом будет обработана первой, а если нет, то продолжить выполнение(совместное планирование).

Задача разделить волокна и волокна

Давайте посмотрим, как работает react под stack-reconciler. Создайте (или обновите) некоторые элементы в коде, react создаст (или обновит) Virtual DOM на основе этих элементов, а затем react изменит реальный DOM в соответствии с разницей между виртуальным DOM до и после обновления. Уведомление,При согласовании стека обновление DOM происходит синхронно, то есть во время сравнения виртуального DOM, если обнаруживается, что экземпляр обновляется, операция DOM будет выполняться немедленно..

Под файбер-консилером операция может быть разделена на множество мелких частей и может быть прервана, поэтому синхронная работа DOM может привести к рассинхронизации файбер-дерева с реальным DOM. Для каждого узла он не только хранит основную информацию о соответствующем элементе, но также хранит некоторую информацию для планирования задач. Таким образом, волокно — это просто объект, представляющий наименьшую единицу работы, которую можно разделить на этапе согласования, что соответствует экземпляру реакции на приведенном выше рисунке. пройти черезstateNodeСвойства управляют характеристиками самого экземпляра. Следующая рабочая единица текущей рабочей единицы представлена ​​дочерней и родственной единицей, а return представляет цель, которая будет объединена с возвращенным результатом после завершения обработки, обычно указывая на родительский узел. Вся структура представляет собой дерево связанных списков. После выполнения каждой рабочей единицы (файбера) он проверяет, есть ли у нее квант времени основного потока, если да, то переходит к следующему, если нет, то сначала обрабатывает другие высокоприоритетные транзакции и ждет, пока основной поток завершится. бездействовать, чтобы продолжить выполнение.

fiber {
  	stateNode: {},
    child: {},
    return: {},
    sibling: {},
}

Например

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

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

В то же время реакция также поддерживает workInProgressTree. workInProgressTree используется для расчета обновлений и завершения процесса согласования.

После того, как пользователь нажмет квадратную кнопку, используйте квадратный список каждого элемента для вызова setState, и реакция отправит текущее обновление в очередь обновлений, соответствующую компоненту списка. Но реакция не сразу выполняет сравнение и модификацию DOM. Вместо этого он передается планировщику для обработки.

Планировщик будет обрабатывать это обновление в соответствии с текущим использованием основного потока. Чтобы реализовать эту функцию, используйтеrequestIdelCallbackAPI. Для браузеров, которые не поддерживают этот API, React добавит pollyfill.

Вообще говоря, обычно, когда клиентский поток выполняет задачи, он будет разделен в виде кадров.Большинство устройств управляются на 30-60 кадрах, не влияя на работу пользователя; между двумя кадрами выполнения основной поток обычно имеет небольшой отрезок свободное время,requestIdleCallbackможно найти в этомПериод простояперечислитьПростой обратный вызов, выполнить некоторые задания

  1. малоприоритетные задачиrequestIdleCallbackиметь дело с;
  2. Высокоприоритетные задачи, такие как связанные с анимациейrequestAnimationFrameиметь дело с;
  3. requestIdleCallbackОбратный вызов периода простоя может вызываться в течение нескольких периодов простоя для выполнения задач;
  4. requestIdleCallbackМетод обеспечивает крайний срок, то есть ограничение времени выполнения задачи, чтобы разделить задачу, избежать длительного выполнения, заблокировать отрисовку пользовательского интерфейса и вызвать выпадение кадров;

Как только процесс согласования получает квант времени, он начинает входить в рабочий цикл. Механизм рабочего цикла позволяет React переключаться между вычислительным состоянием и состоянием ожидания. Для этого для каждого цикла необходимо отслеживать две вещи: следующую единицу работы (следующее обрабатываемое волокно) и время, которое в данный момент может занять основной поток. Первый цикл, следующая обрабатываемая единица — это корневой узел.

Поскольку очередь обновлений на корневом узле пуста, скопируйте корневой узел непосредственно из дерева волокон в workInProgressTree. Корневой узел содержит указатели на дочерние узлы (список).

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

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

Затем войдите в рабочий цикл, который обрабатывает список, и список содержит обновления, поэтому в это время реакция будет вызывать переданный setState.updater funcitonПолучите последнее значение состояния, которое должно быть [1,4,9]. Обычно мы передаем объект при вызове setState, но при использовании файбер-консилера мы должны передать функцию, а возвращаемое значение функции — это состояние, которое нужно обновить. React поддерживает этот способ написания с самых ранних версий, но обычно его никто не использует. В более поздних версиях реакции способ прямой передачи объекта может быть признан устаревшим.

setState({}, callback); // stack conciler
setState(() => { return {} }, callback); // fiber conciler

Получив последнее значение состояния, react обновит значения состояния и свойств списка, затем вызовет рендеринг, а затем получит набор значений, сгенерированный обновленным значением списка.elements. React решит, можно ли повторно использовать волокно, в зависимости от типа сгенерированных элементов. Для текущей ситуации тип вновь сгенерированных элементов не изменился (это по-прежнему Button и Item), поэтому react будет напрямую копировать файбер, соответствующий этим элементам, из файбер-дерева в workInProgress. И пометьте список, потому что это узел, который необходимо обновить.

После обработки узла List реакция по-прежнему будет проверять, достаточно ли текущего кванта времени. Если достаточно, обработайте следующую, которая является кнопкой. Добавьте это, когда пользователь нажимает кнопку, чтобы увеличить шрифт. Эта операция увеличения шрифта реализована чисто js и не имеет ничего общего с реагированием. Однако операция вступает в силу не сразу, так как квант времени реакции не израсходован, поэтому необходимо продолжать обработку кнопки.

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

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

Далее обрабатывается первый элемент. Через хук shouldComponentUpdate можно определить, нужно ли его менять в соответствии с поступающими реквизитами. Для первого элемента это 1 до и после изменения, поэтому он не изменится, shouldComponentUpdate возвращает false, скопируйте div, процесс завершен, проверьте время и введите второй элемент, если еще есть время.

Второй Item shouldComponentUpdate возвращает true, поэтому его нужно пометить, логотип нужно обновить, скопировать div, вызвать рендеринг и обновить содержимое в div с 2 на 4, так как div обновился, поэтому отмечаем разд. Обработка текущего узла завершена.

В приведенном выше случае div уже является листовым узлом без каких-либо одноуровневых узлов, и его значение было обновлено.В настоящее время эффект, создаваемый изменением этого узла, необходимо объединить с родительским узлом. На этом этапе react будет вести список всех элементов, производящих эффекты.

После слияния вернитесь к элементу родительского узла, и маркировка родительского узла завершена.

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

Затем основной поток выполняет операцию увеличения шрифта. После завершения выполните следующую операцию реакции, которая практически аналогична потоку обработки предыдущего Item.После завершения обработки все волокно-дерево и workInProgress выглядят следующим образом:

Когда это сделано, элемент возвращается в список и эффект слияния, список эффектов теперь выглядит следующим образом:

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

На этом этапе нам нужно проверить, достаточно ли времени.Если времени нет, мы подождем, пока не наступит время, чтобы отправить изменения в DOM. После входа на этап 2 reacDOM обновит DOM в соответствии со списком эффектов, рассчитанным на этапе 1.

После обновления DOM workInProgress полностью согласуется с DOM.Чтобы сохранить текущее дерево волокон и DOM, react обменивается указателями current и workinProgress.

На самом деле большую часть времени react поддерживает два дерева (двойная буферизация). Это может сократить время выделения памяти и очистки от мусора в следующем обновлении. После завершения фиксации выполните функцию componentDidMount.

резюме

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

приоритет

module.exports = {
  NoWork: 0, // No work is pending.
  SynchronousPriority: 1, // For controlled text inputs. Synchronous side-effects.
  AnimationPriority: 2, // Needs to complete before the next frame.
  HighPriority: 3, // Interaction that needs to complete pretty soon to feel responsive.
  LowPriority: 4, // Data fetching, or result from updating stores.
  OffscreenPriority: 5, // Won't be visible but do the work in case it becomes visible.
};

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

Это может создать две проблемы:

  • Голодание: решение в эксперименте заключается в повторном использовании, то есть, если высокоприоритетная операция не изменяет узел, на котором была завершена низкоприоритетная операция, то эту часть работы можно использовать повторно.
  • Рендеринг может вызывать несколько функций цикла объявления

функция жизненного цикла

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

Ссылаться на

Reconciliation

Fiber reconciler

совместное планирование

Lin Clark presentation in ReactConf 2017