Давно запущена версия React 16, предложены новые фичи включая Portal и границы исключений.Самое главное переписать алгоритм сверки и запустить новую версию реализации алгоритма-Fiber, поэтому блогер взял три недели, чтобы изучить архитектуру Fiber в свободное время. Реализация и исходный код, иметь предварительное представление о Fiber в целом, подвести итоги и поделиться им. Если вас не интересует какой-то исходный код, вы можете его пропустить. Кроме того , уровень блоггеров ограничен, если что не так, поправьте меня.
предисловие
React позиционируется как библиотека классов JavaScript для создания пользовательских интерфейсов. Он использует язык JavaScript для разработки компонентов пользовательского интерфейса. Он может отображать эти компоненты различными способами и выводить пользовательские интерфейсы. -платформенная совместимость и повторное использование:
Мы не делаем предположений об остальном стеке ваших технологий, поэтому вы можете разрабатывать новые функции в React, не переписывая существующий код.
Теперь React очень хорошо показал себя в следующих аспектах:
- Разработка пользовательского интерфейса веб-приложения React;
- Разработка пользовательского интерфейса приложения React Native;
- Рендеринг на стороне сервера Node.js;
В этих разных сценариях основная часть рендеринга, очевидно, отличается, например, рендеринг DOM веб-приложений, собственный рендеринг View для React Native, рендеринг строк на стороне сервера и т. д. Чтобы быть совместимым и адаптироваться к множеству различных сред рендеринга, очевидно, React не может ограничивать фиксированный способ отображения пользовательского интерфейса.
Основной контент React включает только контент и API, связанные с определением компонентов,Исходный код можно посмотреть, в реальном проекте вы можете видеть, что сначала необходимо использовать следующий код:
import React from 'react';
Что делает этот код, так это вводит модуль исходного кода ядра React.
оказывать
Как упоминалось в предыдущем разделе, основной контент React включает только то, как определять компоненты, и не включает рендеринг конкретных компонентов (т. е. вывод пользовательского интерфейса).Для этого требуются дополнительные модули рендеринга для рендеринга компонентов, определенных React:
-
Модуль рендеринга React DOM: визуализирует компоненты React в DOM, которые затем могут быть обработаны браузером и представлены пользователю, что обычно вводится в веб-приложениях.
react-dom
Модуль:import React from 'react';
import { render } from 'react-dom';
import App from './apps/App.js';
render(
<App />,
document.getElementById('mainBox')
);Как и выше код,
App
это компонент, определенный с использованием основных модулей React, а затем с использованиемreact-dom
предоставляется модулем рендерингаrender
метод для отображения его в качестве вывода DOM на страницу. -
Рендеринг React Native: рендеринг компонентов React в виде мобильных нативных представлений, которые представлены в приложениях React Native.
react-native
Модуль, который предоставляет соответствующие методы рендеринга для рендеринга компонентов React:import { AppRegistry } from 'react-native';
import App from './src/app.js';
AppRegistry.registerComponent('fuc', () => App);Как указано выше,
App
является корневым компонентом React, использующимreact-native
рендерераAppRegistry.registerComponent
метод, чтобы отобразить его как собственное представление. -
Тестовый рендеринг React: рендеринг компонентов React в виде дерева JSON для завершенияJestизснимок теста, содержимое находится в
react-test-renderer
Модуль:import ReactTestRenderer from 'react-test-renderer';
const renderer = ReactTestRenderer.create(
<Link page="https://www.facebook.com/">Facebook</Link>
);
console.log(renderer.toJSON());
// { type: 'a',
// props: { href: 'https://www.facebook.com/' },
// children: [ 'Facebook' ] } -
Рендеринг векторной графики React: Рендеринг компонентов React в соответствующие соответствующие карты (ARTбиблиотека);
Веб-приложение React является наиболее распространенным и простым для понимания, поэтому оставшаяся часть этой статьи будет анализировать Fiber с точки зрения средства визуализации React-DOM.
Примирение
Как упоминалось в предыдущих двух разделах, ядром React является определение компонентов, а способ рендеринга компонентов определяется средой.Определение компонентов, управление состоянием компонентов, управление методами жизненного цикла и обновления компонентов должны обрабатываться последовательно. на разных платформах и не зависят от среды рендеринга. Эта часть контента, объединеннаяПримирительиметь дело с,передача исходного кода, который используется разными рендерерами. Основная функция согласователя — вызывать каждый компонент дерева компонентов при изменении состояния компонента.render
методы, рендеринг, выгрузка компонентов.
Stack Reconciler
Мы знаем, что браузерный движок рендеринга однопоточный, в React 15.x и более ранних версиях весь поток будет блокироваться при изменении дерева вычислительных компонентов, весь процесс рендеринга выполняется непрерывно без перерыва, а другие задачи в это время будут Блокировка, например анимация и т. д., которая может заставить пользователей чувствовать себя явно застрявшими. Например, когда вы посещаете веб-сайт и вводите определенное ключевое слово для поиска, приоритет должен быть отдан интерактивной обратной связи или анимационным эффектам. Если интерактивная обратная связь задерживается на 200 мс, пользователь почувствует более очевидное зависание, а задержка ответа данных в 200 миллисекунд не является большой проблемой. Эту версию согласователя можно назвать согласователем стека, и общий процесс его алгоритма согласования можно найти вАлгоритм реагирования на различияиРеализация React Stack Reconciler.
Основной недостаток Stack Reconcilier заключается в том, что он не может ни приостанавливать задачи рендеринга, ни разделять задачи, а также не может эффективно сбалансировать порядок выполнения между рендерингом обновления компонентов и задачами, связанными с анимацией, т. задачи останавливаются, кадры анимации пропадают и другие проблемы.
Fiber Reconciler
Версия React 16 предлагает более продвинутый согласователь, который позволяет выполнять процесс рендеринга по частям, а не выполнять все сразу, и может возвращаться к основному управлению процессом для выполнения других задач в середине. Это делается путем расчета изменений в некоторых деревьях компонентов, приостановки рендеринга и обновления и запроса основного процесса, есть ли задачи рисования или обновления с более высоким спросом, которые необходимо выполнить, и рендеринг начинается после завершения этих задач с высоким спросом. Все это достигается за счет введения новой структуры данных - объекта Fiber в слой кода. Каждый экземпляр компонента соответствует экземпляру волокна. Этот экземпляр волокна отвечает за управление обновлением экземпляра компонента, задачами рендеринга и связью с другими экземплярами волокна. .
Этот новый согласователь называется Fiber Reconciler, и он предоставляет следующие новые функции:
- Разделяйте и прерывайте задачи;
- Можно повторно использовать поэтапные задачи и устанавливать приоритеты;
- Вы можете переключать задачи вперед и назад между задачами родительского и дочернего компонентов;
-
render
Метод может возвращать несколько элементов (то есть он может возвращать массив); - Исключение обработки границы исключения поддержки;
Сказав так много, наконец появится главный герой этой статьи: Fiber. Последняя версия React была обновлена до 16.1.1. Предполагается, что стабильная версия 16.x будет не за горами. Позвольте нам иметь быстрый взгляд.
Волокно и JavaScript
Как упоминалось ранее, Fiber может обеспечить согласованное выполнение задач с разными приоритетами асинхронно, поэтому для средства визуализации DOM этот метод предоставляется на уровне JavaScript или его можно только смоделировать с помощью setTimeout? В настоящее время новая версия основных браузеров предоставляет доступные API:requestIdleCallback
иrequestAnimationFrame
:
- requestIdleCallback: запланировать выполнение низкоприоритетных функций в период простоя потока;
- requestAnimationFrame: Запланировать выполнение высокоприоритетных функций в следующем кадре анимации;
Период простоя
Обычно, когда клиентский поток выполняет задачи, он будет разделен на фреймы.Большинство устройств управляются на 30-60 фреймах, не влияя на пользовательский опыт; между двумя фреймами выполнения основной поток обычно имеет небольшое время простоя.requestIdleCallback
можно найти в этомПериод простояперечислитьПростой обратный вызов, выполнить некоторые задания.
Fiber и requestIdleCallback
Что делает Fiber, так это то, что ему нужно декомпозировать задачи рендеринга, а затем использовать планирование API в соответствии с приоритетом для асинхронного выполнения определенных задач:
- малоприоритетные задачи
requestIdleCallback
иметь дело с; - Высокоприоритетные задачи, такие как связанные с анимацией
requestAnimationFrame
иметь дело с; -
requestIdleCallback
Обратный вызов периода простоя может вызываться в течение нескольких периодов простоя для выполнения задач; -
requestIdleCallback
Метод обеспечивает крайний срок, то есть ограничение времени выполнения задачи, чтобы разделить задачу, избежать длительного выполнения, заблокировать отрисовку пользовательского интерфейса и вызвать выпадение кадров;
конкретныйВыполнение задач для передачи исходного кода:
-
Если собственный API поддерживается, см. приведенную выше ссылку для конкретной собственной реализации:
rIC = window.requestIdleCallback;
cIC = window.cancelIdleCallback;
export {now, rIC, cIC}; -
Если не поддерживается, пользовательская реализация:
let isIdleScheduled = false; // 是否在执行空闲期回调
let frameDeadlineObject = {
didTimeout: false,
timeRemaining() {
// now = Performance.now || Date.now
const remaining = frameDeadline - now();
// 计算得到当前帧运行剩余时间
return remaining > 0 ? remaining : 0;
},
};
// 帧回调
const animationTick = function(rafTime) {
...
if (!isIdleScheduled) {
// 不在执行空闲期回调,表明可以调用空闲期回调
isIdleScheduled = true;
// 执行Idle空闲期回调
idleTick();
}
};
// 空闲期回调
const idleTick = function() {
// 重置为false,表明可以调用空闲期回调
isIdleScheduled = false;
const currentTime = now();
if (frameDeadline - currentTime <= 0) {
// 帧到期时间小于当前时间,说明已过期
if (timeoutTime !== -1 && timeoutTime <= currentTime) {
// 此帧已过期,且发生任务处理函数(执行具体任务,传入的回调)的超时
// 需要执行任务处理,下文将调用;
frameDeadlineObject.didTimeout = true;
} else {
// 帧已过期,但没有发生任务处理函数的超时,暂时不调用任务处理函数
if (!isAnimationFrameScheduled) {
// 当前没有调度别的帧回调函数
// 调度下一帧
isAnimationFrameScheduled = true;
requestAnimationFrame(animationTick);
}
// Exit without invoking the callback.
return;
}
} else {
// 这一帧还有剩余时间
// 标记未超时,之后调用任务处理函数
frameDeadlineObject.didTimeout = false;
}
// 缓存的任务处理函数
timeoutTime = -1;
const callback = scheduledRICCallback;
scheduledRICCallback = null;
if (callback !== null) {
// 执行回调
callback(frameDeadlineObject);
}
}
// 自定义模拟requestIdleCallback
rIC = function(
callback: (deadline: Deadline) => void, // 传入的任务处理函数参数
options?: {timeout: number} // 其他参数
) {
// 回调函数
scheduledRICCallback = callback;
if (options != null && typeof options.timeout === 'number') {
// 计算过期时间
timeoutTime = now() + options.timeout;
}
if (!isAnimationFrameScheduled) {
// 当前没有调度别的帧回调函数
isAnimationFrameScheduled = true;
// 初始开始执行帧回调
requestAnimationFrame(animationTick);
}
return 0;
};-
frameDeadline
: время ограничения кадров, которое больше подходит для текущей среды и регулируется от 30 кадров в секунду (т. е. 30 кадров) на основе эвристики; -
timeRemaining
: рассчитатьrequestIdleCallback
Оставшееся время простоя (кадра) выполнения задачи, то есть время от дедлайна; -
options.timeout
: Внутренний вызов оптоволокнаrIC
Когда API выполняет асинхронную задачу, передается параметр времени истечения срока действия задачи; -
frameDeadlineObject
: Рассчитанный объект доступного времени определенного кадра, два атрибута соответственно представляют:- didTimeout: истекло ли время ожидания обработчика входящей асинхронной задачи;
- timeRemaining: оставшееся время простоя функции обработки исполняемой задачи текущего кадра;
-
frameDeadlineObject
Объект основан на поступающихtimeout
параметры и внутренняя самонастройка этого модуляframeDeadline
рассчитываются параметры;
-
Волокно и компоненты
Мы уже знаем функцию волокна и его основные особенности, поэтому, как оно связано с компонентом и как добиться эффекта, можно резюмировать следующие моменты:
- Основной единицей приложения React является компонент, и приложение организовано в виде дерева компонентов для рендеринга компонентов;
- Базовой единицей Fiber blender является волокно (гармонизирующий узел), приложение организовано в виде дерева волокон, применяется алгоритм Fiber;
- Дерево компонентов соответствует структуре дерева волокон, а экземпляр компонента имеет соответствующий экземпляр волокна;
- Файбер отвечает за согласование всего прикладного уровня, а экземпляр файбера отвечает за согласование соответствующих компонентов;
Обратите внимание на разницу между волокном и волокном.Волокно относится к алгоритму смешивания, а волокно является единицей алгоритма смешивания.Подобно отношениям между компонентами и приложениями, каждый экземпляр компонента будет иметь соответствующий экземпляр волокна, отвечающий за согласование компонента.
Структура данных волокна
До сих пор у нас должно быть предварительное представление о Fiber.Прежде чем подробно представить реализацию и архитектуру Fiber, мы собираемся кратко представить структуру данных Fiber.Структура данных может в определенной степени отражать его общую рабочую структуру.
По сути, файбер — это объект JavaScript, в котором хранится информация о связанном компоненте в виде пары ключ-значение, включая пропсы, полученные компонентом, поддерживаемое состояние и контент, который необходимо отобразить в конце. Далее мы представим основные свойства объекта Fiber.
Волоконный объект
во-первыхВолоконный объектопределяется следующим образом:
// 一个Fiber对象作用于一个组件
export type Fiber = {|
// 标记fiber类型tag.
tag: TypeOfWork,
// fiber对应的function/class/module类型组件名.
type: any,
// fiber所在组件树的根组件FiberRoot对象
stateNode: any,
// 处理完当前fiber后返回的fiber,
// 返回当前fiber所在fiber树的父级fiber实例
return: Fiber | null,
// fiber树结构相关链接
child: Fiber | null,
sibling: Fiber | null,
index: number,
// 当前处理过程中的组件props对象
pendingProps: any,
// 缓存的之前组件props对象
memoizedProps: any, // The props used to create the output.
// The state used to create the output
memoizedState: any,
// 组件状态更新及对应回调函数的存储队列
updateQueue: UpdateQueue<any> | null,
// 描述当前fiber实例及其子fiber树的数位,
// 如,AsyncUpdates特殊字表示默认以异步形式处理子树,
// 一个fiber实例创建时,此属性继承自父级fiber,在创建时也可以修改值,
// 但随后将不可修改。
internalContextTag: TypeOfInternalContext,
// 更新任务的最晚执行时间
expirationTime: ExpirationTime,
// fiber的版本池,即记录fiber更新过程,便于恢复
alternate: Fiber | null,
// Conceptual aliases
// workInProgress : Fiber -> alternate The alternate used for reuse happens
// to be the same as work in progress.
|};
- тип и ключ: то же, что и значение элемента React;
- type: описывает компонент React, соответствующий файберу;
- Для составных компонентов: значением является сама функция или компонент класса;
- Для нативных компонентов (div и т. д.): значением является строка типа элемента;
- ключ: фаза согласования, которая идентифицирует волокно, чтобы определить, можно ли повторно использовать экземпляр волокна;
- дочерний и одноуровневый: дерево компонентов, соответствующее поколению дерева волокон, отношения аналогии;
- pendingProps и memoizedProps: представляет текущие входящие и предыдущие реквизиты компонента соответственно;
- return: возвращает экземпляр родительского волокна дерева волокон, в котором находится текущее волокно, то есть волокно, соответствующее родительскому компоненту текущего компонента;
- Альтернативный: пул версий волокна, то есть для записи процесса обновления волокна для легкого восстановления и повторного использования;
- workInProgress: обрабатываемое волокно, концептуально называемое, на самом деле не имеет этого атрибута;
alternate fiber
Его можно понимать как пул версий файбера, который используется для поочередной записи обновлений файбера в процессе обновления компонента (после разделения задач на многоэтапное обновление), потому что на каждом этапе обновления компонента статус файбера несогласован до и в процессе обновления.Когда требуется восстановление (например, возникает конфликт), другой можно использовать для прямого возврата к предыдущей версии волокна.
- Используйте альтернативный атрибут, чтобы соединить текущее волокно и его незавершенное производство в двух направлениях, альтернативный атрибут текущего экземпляра волокна указывает на его незавершенное производство, а альтернативный атрибут незавершенного производства указывает на текущий стабильное волокно;
- Замещающая версия текущего волокна является его незавершенной работой, а альтернативная версия незавершенной работы — текущим волокном;
- Когда незавершенная работа обновляется один раз, она будет синхронизирована с текущим волокном, а затем продолжится до завершения задачи;
- work-in-progress указывает на обрабатываемое волокно, а текущее волокно всегда поддерживает последнюю версию обрабатываемого волокна.
Создать экземпляр волокна
Создание экземпляра волокна возвращает объект JavaScript со многими свойствами, описанными в предыдущем разделе.FiberNode
То есть построить и вернуть инициализированный объект в соответствии с переданными параметрами:
var createFiber = function(
tag: TypeOfWork,
key: null | string,
internalContextTag: TypeOfInternalContext,
) {
return new FiberNode(tag, key, internalContextTag);
};
Реализация создания альтернативных волокон для обработки задач выглядит следующим образом:
// 创建一个alternate fiber处理任务
export function createWorkInProgress(
current: Fiber,
pendingProps: any,
expirationTime: ExpirationTime,
) {
let workInProgress = current.alternate;
if (workInProgress === null) {
workInProgress = createFiber(
current.tag,
current.key,
current.internalContextTag,
);
workInProgress.type = current.type;
workInProgress.stateNode = current.stateNode;
// 形成alternate关系,互相交替模拟版本池
workInProgress.alternate = current;
current.alternate = workInProgress;
}
workInProgress.expirationTime = expirationTime;
workInProgress.pendingProps = pendingProps;
workInProgress.child = current.child;
workInProgress.memoizedProps = current.memoizedProps;
workInProgress.memoizedState = current.memoizedState;
workInProgress.updateQueue = current.updateQueue;
...
return workInProgress;
}
Тип волокна
В предыдущем разделе объект Fiber имелtag
Атрибут, обозначающий тип волокна, и экземпляр волокна соответствует компоненту, поэтому его тип в основном соответствует типу компонента, см. исходный кодМодуль ReactTypeOfWork:
export type TypeOfWork = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;
export const IndeterminateComponent = 0; // 尚不知是类组件还是函数式组件
export const FunctionalComponent = 1; // 函数式组件
export const ClassComponent = 2; // Class类组件
export const HostRoot = 3; // 组件树根组件,可以嵌套
export const HostPortal = 4; // 子树. Could be an entry point to a different renderer.
export const HostComponent = 5; // 标准组件,如地div, span等
export const HostText = 6; // 文本
export const CallComponent = 7; // 组件调用
export const CallHandlerPhase = 8; // 调用组件方法
export const ReturnComponent = 9; // placeholder(占位符)
export const Fragment = 10; // 片段
При планировании и выполнении задач будет выполняться различная обработка в соответствии с разными типами волокон, то есть значением fiber.tag.
Объект FiberRoot
FiberRoot
Object, который в основном используется для управления процессом обновления компонентов дерева компонентов и записи соответствующей информации о контейнере DOM, смонтированном в дереве компонентов. Конкретные определения см.Модуль ReactFiberRoot:
export type FiberRoot = {
// fiber节点的容器元素相关信息,通常会直接传入容器元素
containerInfo: any,
// 当前fiber树中激活状态(正在处理)的fiber节点,
current: Fiber,
// 此节点剩余的任务到期时间
remainingExpirationTime: ExpirationTime,
// 更新是否可以提交
isReadyForCommit: boolean,
// 准备好提交的已处理完成的work-in-progress
finishedWork: Fiber | null,
// 多组件树FirberRoot对象以单链表存储链接,指向下一个需要调度的FiberRoot
nextScheduledRoot: FiberRoot | null,
};
Создать экземпляр FiberRoot
import {
ClassComponent,
HostRoot
} from 'shared/ReactTypeOfWork';
// 创建返回一个初始根组件对应的fiber实例
function createHostRootFiber(): Fiber {
// 创建fiber
const fiber = createFiber(HostRoot, null, NoContext);
return fiber;
}
export function createFiberRoot(
containerInfo: any,
hydrate: boolean,
) {
// 创建初始根组件对应的fiber实例
const uninitializedFiber = createHostRootFiber();
// 组件树根组件的FiberRoot对象
const root = {
// 根组件对应的fiber实例
current: uninitializedFiber,
containerInfo: containerInfo,
pendingChildren: null,
remainingExpirationTime: NoWork,
isReadyForCommit: false,
finishedWork: null,
context: null,
pendingContext: null,
hydrate,
nextScheduledRoot: null,
};
// 组件树根组件fiber实例的stateNode指向FiberRoot对象
uninitializedFiber.stateNode = root;
return root;
}
ReactChildFiber
После создания объекта FiberRoot дерева компонентов будут созданы соответствующие экземпляры волокон для подкомпонентов.Эта часть состоит изМодуль ReactChildFiberвыполнить:
// 调和(处理更新)子fibers
export const reconcileChildFibers = ChildReconciler(true);
// 挂载(初始化)子fibers
export const mountChildFibers = ChildReconciler(false);
ChildReconciler
Этот метод определяет, следует ли вызывать логику инициализации волокон подкомпонента или выполнять логику согласования существующих волокон подкомпонента в соответствии с входящими параметрами.
ChildReconciler
метод, возвращаетreconcileChildFibers
метод:
- Определить тип данных контента, переданного дочерним элементом, и выполнить различную обработку, которая также соответствует передаче, когда мы пишем компоненты React.
props.children
, его тип может быть объектом или массивом, строкой, числом и т. д.; - Затем, в зависимости от типа подкомпонента, вызываются различные специальные функции обработки согласования;
- Наконец, верните экземпляр волокна, созданный или обновленный в соответствии с подкомпонентом;
function ChildReconciler(a) {
function reconcileChildFibers(
returnFiber: Fiber, currentFirstChild: Fiber | null,
newChild: any, expirationTime: ExpirationTime,
) {
// Handle object types
const isObject = typeof newChild === 'object' && newChild !== null;
if (isObject) {
// 子组件实例类型,以Symbol符号表示的
switch (newChild.?typeof) {
// React Element
case REACT_ELEMENT_TYPE:
return placeSingleChild(
reconcileSingleElement(
returnFiber, currentFirstChild,
newChild, expirationTime
)
);
// React组件调用
case REACT_CALL_TYPE:
return placeSingleChild(reconcileSingleCall(...));
// placeholder
case REACT_RETURN_TYPE:
return ...;
case REACT_PORTAL_TYPE:
return ...;
}
}
if (typeof newChild === 'string' || typeof newChild === 'number') {
return placeSingleChild(reconcileSingleTextNode(...));
}
if (isArray(newChild)) {
return reconcileChildrenArray(...);
}
if (getIteratorFn(newChild)) {
return reconcileChildrenIterator(...);
}
...
}
}
Волоконная архитектура
Когда я изучал Файбер, я пытался читать исходный код и обнаружил, что его сложно быстро понять таким образом.Чтобы изучить Файбер, я должен сначала понять, что делает блендер и как он существует в Реакте, а затем узнать о структура и структура Fiber.Идея реализации алгоритма, понимание того, что он должен делать от определения компонента до рендеринга на страницу, также является организационной формой этой статьи.
Приоритет (ExpirationTime VS PriorityLevel)
Мы уже знаем, что Fiber может делить задачи и ставить разные приоритеты, так как же добиться разделения по приоритетам и в чем это проявляется?
ExpirationTime
Оптоволокно разделяет задачи и вызовыrequestIdleCallback
иrequestAnimationFrame
API гарантирует, что задачи рендеринга и другие задачи могут выполняться стабильно, не влияя на взаимодействие приложений и пропадание кадров.Способ достижения планирования заключается в том, чтобы установить время истечения срока действия для каждого экземпляра волокна, а разное время представляет разные приоритеты. Чем короче срок действия время, тем выше приоритет и должен быть выполнен как можно скорее.
Так называемое время истечения (ExpirationTime) — это период времени относительно времени начала начального вызова планировщика, в течение определенного периода времени после начального вызова планировщика обновление необходимо запланировать для завершения. является значением времени экспирации.
Волокно обеспечиваетReactFiberExpirationTimeВ модуле реализовано определение времени экспирации:
export const NoWork = 0; // 没有任务等待处理
export const Sync = 1; // 同步模式,立即处理任务
export const Never = 2147483647; // Max int32: Math.pow(2, 31) - 1
const UNIT_SIZE = 10; // 过期时间单元(ms)
const MAGIC_NUMBER_OFFSET = 2; // 到期时间偏移量
// 以ExpirationTime特定单位(1单位=10ms)表示的到期执行时间
// 1 unit of expiration time represents 10ms.
export function msToExpirationTime (ms) {
// 总是增加一个偏移量,在ms<10时与Nowork模式进行区别
return ((ms / UNIT_SIZE) | 0) + MAGIC_NUMBER_OFFSET;
}
// 以毫秒表示的到期执行时间
export function expirationTimeToMs(expirationTime: ExpirationTime) {
return (expirationTime - MAGIC_NUMBER_OFFSET) * UNIT_SIZE;
}
// 向上取整(整数单位到期执行时间)
// precision范围精度:弥补任务执行时间误差
function ceiling(num, precision) {
return (((num / precision) | 0) + 1) * precision;
}
// 计算处理误差时间在内的到期时间
export function computeExpirationBucket(currentTime, expirationInMs, bucketSizeMs,) {
return ceiling(
currentTime + expirationInMs / UNIT_SIZE,
bucketSizeMs / UNIT_SIZE
);
}
Функции, предоставляемые этим модулем, в основном включают:
- Синхронизация: синхронный режим, такие задачи выполняются непосредственно в потоке пользовательского интерфейса, например обратная связь анимации;
- Асинхронный режим:
- Преобразование: Взаимное преобразование между определенной единицей времени экспирации и единицей времени (мс);
- Расчет: Рассчитайте время истечения срока действия, включая допустимую ошибку;
PriorityLevel
На самом деле в версии 15.x существует иерархия приоритетов задач.Модуль ReactPriorityLevel:
export type PriorityLevel = 0 | 1 | 2 | 3 | 4 | 5;
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.
};
По сравнению с простым иерархическим разделением PriorityLevel метод времени истечения срока действия ExpirationTime используется в версии 16.x для указания приоритета задач, что позволяет лучше разделять и планировать задачи.
Планировщик
Основная функция согласователя — вызывать компоненты дерева компонентов при изменении состояния компонента.render
Компоненты методов, рендеринга и выгрузки, а также Fiber позволяют приложениям лучше координировать выполнение разных задач.Реализацию эффективной координации в посреднике можно назвать планировщиком.
Как следует из названия, планировщик планирует ресурсы для выполнения определенных задач. Обновление и рендеринг компонентов приложения в приложениях React требуют системных ресурсов ЦП. Если баланс ресурсов, разумное планирование и стратегии выполнения задач не могут быть хорошо сбалансированы, это легко вызвать Проблемы с процессором.Потребление и трата этого дефицитного ресурса может вызвать множество проблем, таких как зависание страницы, выпадение кадров анимации, ненормальное обновление компонентов и т. д. быть перегруженным.
В версии React 15.x изменение состояния компонента напрямую приведет к повторному рендерингу дерева его подкомпонентов.Новая версия алгоритма Fiber всесторонне улучшит планировщик.Основные проблемы:
- Объединение нескольких обновлений: нет необходимости запускать задачу обновления немедленно при изменении каждого состояния компонента.Некоторые промежуточные изменения состояния на самом деле являются пустой тратой ресурсов, потребляемых задачей обновления.От A до B, а затем до C, изменение состояния B в середине на самом деле не имеет смысла для пользователя, тогда мы можем напрямую объединить изменение состояния и запустить только одно обновление непосредственно из A в C;
- Приоритет задачи: разные типы обновлений имеют разные приоритеты. Например, интерактивная анимация, вызванная действиями пользователя, может требовать лучшего восприятия, и их приоритет должен быть выше, чем выполнение обновлений данных;
- Планирование push-pull: метод планирования на основе push требует, чтобы разработчики косвенно кодировали, чтобы определить, как планировать задачи, в то время как планирование типа pull более удобно для уровня инфраструктуры React для непосредственного выполнения глобального автономного планирования;
Отправить для просмотра исходного кода
export default function () {
...
return {
computeAsyncExpiration,
computeExpirationForFiber,
scheduleWork,
batchedUpdates,
unbatchedUpdates,
flushSync,
deferredUpdates,
};
}
Основным выходным API вышеуказанного планировщика является реализация таких функций, как планирование задач, получение обновлений и задержка обновлений.
Планировщик и приоритет
Как планировщик делит задачи и расставляет приоритеты? В алгоритме согласования React задачи описываются экземплярами файберов, поэтому приоритизация задач эквивалентна установке времени истечения файбера (expirationTime), которое предоставляется в планировщикеcomputeExpirationForFiber
метод расчета времени истечения волокна:
import {
NoWork, Sync, Never, msToExpirationTime,
expirationTimeToMs, computeExpirationBucket
} from './ReactFiberExpirationTime';
// 表示下一个要处理的任务的到期时间,默认为NoWork,即当前没有正在等待执行的任务;
// Nowork默认更新策略:异步模式下,异步执行任务;同步模式下同步执行任务
let expirationContext = NoWork;
// 下一次渲染到期时间
let nextRenderExpirationTime = NoWork;
// 异步更新
export const AsyncUpdates = 1;
// 初始时间(ms).
const startTime = now();
// ExpirationTime单位表示的当前时间(ExpirationTime单位,初始值传入0)
let mostRecentCurrentTime = msToExpirationTime(0);
// 计算fiber的到期时间
function computeExpirationForFiber(fiber) {
let expirationTime;
if (isWorking) {
if (isCommitting) {
// 在提交阶段的更新任务
// 需要明确设置同步优先级(Sync Priority)
expirationTime = Sync;
} else {
// 在渲染阶段发生的更新任务
// 需要设置为下一次渲染时间的到期时间优先级
expirationTime = nextRenderExpirationTime;
}
} else {
// 不在任务执行阶段,需要计算新的过期时间
// 明确传递useSyncScheduling为true表明期望同步调用
// 且fiber.internalContextTag != AsyncUpdates
if (useSyncScheduling && !(fiber.internalContextTag & AsyncUpdates)) {
// 同步更新,设置为同步标记
expirationTime = Sync;
} else {
// 异步更新,计算异步到期时间
expirationTime = computeAsyncExpiration();
}
}
return expirationTime;
}
- Если в настоящее время он находится на этапе отправки задачи (обновить отправку для рендеринга DOM), установите для текущего времени истечения срока действия волокна значение
Sync
, то есть синхронный режим выполнения; - Если он находится на этапе рендеринга DOM, вам необходимо отложить задачу волокна и установить срок действия волокна равным времени истечения срока действия следующего рендеринга DOM;
- Если он не находится в стадии выполнения задачи, необходимо сбросить время истечения срока действия волокна:
- Если явно установлено
useSyncScheduling
иfiber.internalContextTag
значение не равноAsyncUpdates
, это указывает на синхронный режим, установленный наSync
; - В противном случае позвоните
computeAsyncExpiration
метод пересчета времени истечения этого волокна;
- Если явно установлено
// 重新计算当前时间(ExpirationTime单位表示)
function recalculateCurrentTime() {
const ms = now() - startTime;
// ExpirationTime单位表示的当前时间
// 时间段值为 now() - startTime(起始时间)
mostRecentCurrentTime = msToExpirationTime(ms);
return mostRecentCurrentTime;
}
// 计算异步任务的到期时间
function computeAsyncExpiration() {
// 计算得到ExpirationTime单位的当前时间
// 聚合相似的更新在一起
// 更新应该在 ~1000ms,最多1200ms内完成
const currentTime = recalculateCurrentTime();
// 对于每个fiber的期望到期时间的增值,最大值为1000ms
const expirationMs = 1000;
// 到期时间的可接受误差时间,200ms
const bucketSizeMs = 200;
// 返回包含误差时间在内的到期时间
return computeExpirationBucket(currentTime, expirationMs, bucketSizeMs);
}
Для каждого волокна мы ожидаем, что параметр времени истечения будет равен 1000 мс. Кроме того, из-за ошибки времени выполнения задачи мы принимаем ошибку 200 мс. Возвращаемое значение по умолчанию для окончательно рассчитанного времени истечения — это единица ExpirationTime.
планирование задач
В предыдущем разделе было показано, что планировщик в основном обеспечиваетcomputeExpirationForFiber
и другие методы поддерживают вычисление приоритета задачи (время истечения срока действия).Далее мы представим, как планировщик планирует задачи.
Когда приложение React обновляется, Fiber переходит от текущего узла обработки к корневому компоненту дерева компонентов, а затем начинает обрабатывать обновление, вызывая предыдущий
requestIdleCallback
Подождите, пока API выполнит обработку обновления.
Основная логика планирования реализована вscheduleWork
:
- пройти через
fiber.return
Атрибут, переход от текущего экземпляра волокна к корневому компоненту дерева компонентов; - Время истечения оценивается для каждого экземпляра волокна по очереди.Если оно больше параметра ожидаемого времени истечения входящей задачи, оно будет обновлено до времени истечения входящей задачи;
- перечислить
requestWork
Метод начинает обработку задачи и передает полученный объект FiberRoot корневого компонента дерева компонентов и время истечения срока действия задачи;
// 调度任务
// expirationTime为期望的任务到期时间
function scheduleWork(fiber, expirationTime: ExpirationTime) {
return scheduleWorkImpl(fiber, expirationTime, false);
}
function scheduleWorkImpl(
fiber, expirationTime
) {
let node = fiber;
while (node !== null) {
// 向上遍历至根组件fiber实例,并依次更新expirationTime到期时间
if (
node.expirationTime === NoWork ||
node.expirationTime > expirationTime
) {
// 若fiber实例到期时间大于期望的任务到期时间,则更新fiber到期时间
node.expirationTime = expirationTime;
}
// 同时更新alternate fiber的到期时间
if (node.alternate !== null) {
if (
node.alternate.expirationTime === NoWork ||
node.alternate.expirationTime > expirationTime
) {
// 若alternate fiber到期时间大于期望的任务到期时间,则更新fiber到期时间
node.alternate.expirationTime = expirationTime;
}
}
// node.return为空,说明到达组件树顶部
if (node.return === null) {
if (node.tag === HostRoot) {
// 确保是组件树根组件并获取FiberRoot实例
const root = node.stateNode;
// 请求处理任务
requestWork(root, expirationTime);
} else {
return;
}
}
// 获取父级组件fiber实例
node = node.return;
}
}
обработка задачrequestWork
Метод реализуется следующим образом:
- Сначала сравните оставшееся время истечения задачи с ожидаемым временем истечения срока действия задачи, если оно больше, обновите значение;
- Определите ожидаемое время истечения срока действия задачи (Expirtime) и различать синхронные или асинхронные задачи исполнения;
// 当根节点发生更新时,调度器将调用requestWork方法开始任务处理过程
// It's up to the renderer to call renderRoot at some point in the future.
function requestWork(root: FiberRoot, expirationTime) {
const remainingExpirationTime = root.remainingExpirationTime;
if (remainingExpirationTime === NoWork ||
expirationTime < remainingExpirationTime) {
// 若任务剩余到期时间大于期望的任务到期时间,则需要更新
root.remainingExpirationTime = expirationTime;
}
if (expirationTime === Sync) {
// 同步
performWork(Sync, null);
} else {
// 异步
scheduleCallbackWithExpiration(expirationTime);
}
}
Очередь обновлений (UpdateQueue)
Мы знаем, что если нам нужно реализовать асинхронное обновление компонентов, мы должны сохранить задачу обновления перед обновлением, а затем прочитать обновление и реализовать обновление компонента при запуске асинхронной задачи.Для хранения задачи обновления требуется структура данных, наиболее распространенным является стек и очередь, реализация Fiber представляет собой очередь.
После того, как Fiber разделит задачу на несколько рабочих единиц, ее необходимо определить по приоритету и сохранить в очереди обновлений, а затем запланировать и выполнить в соответствии с приоритетом. Мы знаем, что у каждого компонента есть соответствующий экземпляр волокна, а экземпляр волокна — это единица задачи, отвечающая за управление компонентом планирования, поэтому необходимо поддерживать очередь обновлений для каждого экземпляра волокна компонента.
Очередь обновлений Fiber создаетсяМодуль ReactFiberUpdateQueueРеализация в основном включает в себя:
- Создайте очередь обновлений;
- добавить обновление в очередь обновлений;
- Добавьте обновления в файбер (то есть очередь обновлений, соответствующую экземпляру файбера);
- Обрабатывать обновления в очереди обновлений и возвращать новый объект состояния:
// 一个更新对应的数据结构
export type Update<State> = {
expirationTime: ExpirationTime,
partialState: PartialState<any, any>,
callback: Callback | null,
isReplace: boolean,
isForced: boolean,
next: Update<State> | null,
};
// 更新队列,以单链表形式表示并持久化
// 调度一个更新任务时,将其添加至当前(current)fiber和work-in-progress fiber的更新队列中;
// 这两个更新队列相互独立但共享同一个持久化数据结构;
// work-in-progress更新队列通常是current fiber更新队列的子集;
// 发生调和时,更新任务从work-in-progress fiber更新队列移除,
// current fiber内的更新任务则保留,当work-in-progress中断时可以从current fiber恢复;
// 提交完更新时,work-in-progress fiber就会变成current fiber
export type UpdateQueue<State> = {
// 若存在更早添加至队列的更新未被处理,
// 则此已处理的更新并不会从队列中移除-先进先出原则
// 所以需要维护baseState,代表第一个未处理的更新的基础状态,
// 通常这就是队列中的第一个更新,因为在队列首部的已处理更新会被移除
baseState: State,
// 同理,需要维护最近的未处理的更新的到期时间,
// 即未处理更新中到期时间值最小的
expirationTime: ExpirationTime,
first: Update<State> | null,
last: Update<State> | null,
callbackList: Array<Update<State>> | null,
hasForceUpdate: boolean,
isInitialized: boolean
};
// 添加更新至更新队列
export function insertUpdateIntoQueue<State>(
queue: UpdateQueue<State>,
update: Update<State>
){
// 添加更新至队列尾部
if (queue.last === null) {
// 队列为空
queue.first = queue.last = update;
} else {
queue.last.next = update;
queue.last = update;
}
if (
queue.expirationTime === NoWork ||
queue.expirationTime > update.expirationTime
) {
// 更新最近到期时间
queue.expirationTime = update.expirationTime;
}
}
// 添加更新至fiber实例
export function insertUpdateIntoFiber<State>(
fiber: Fiber,
update: Update<State>,
) {
// 可以创建两个独立的更新队列
// alternate主要用来保存更新过程中各版本更新队列,方便崩溃或冲突时回退
const alternateFiber = fiber.alternate;
let queue1 = fiber.updateQueue;
if (queue1 === null) {
// 更新队列不存在,则创建一个空的更新队列
queue1 = fiber.updateQueue = createUpdateQueue((null));
}
let queue2;
if (alternateFiber !== null) {
// alternate fiber实例存在,则需要为此
queue2 = alternateFiber.updateQueue;
if (queue2 === null) {
queue2 = alternateFiber.updateQueue = createUpdateQueue((null: any));
}
} else {
queue2 = null;
}
queue2 = queue2 !== queue1 ? queue2 : null;
// 如果只存在一个更新队列
if (queue2 === null) {
insertUpdateIntoQueue(queue1, update);
return;
}
// 如果任意更新队列为空,则需要将更新添加至两个更新队列
if (queue1.last === null || queue2.last === null) {
insertUpdateIntoQueue(queue1, update);
insertUpdateIntoQueue(queue2, update);
return;
}
// 如果2个更新队列均非空,则添加更新至第一个队列,并更新另一个队列的尾部更新项
insertUpdateIntoQueue(queue1, update);
queue2.last = update;
}
// 处理更新队列任务,返回新状态对象
export function processUpdateQueue<State>(
current, workInProgress, queue, instance, props,
renderExpirationTime,
) {
if (current !== null && current.updateQueue === queue) {
// 克隆current fiber以创建work-in-progress fiber
const currentQueue = queue;
queue = workInProgress.updateQueue = {
baseState: currentQueue.baseState,
expirationTime: currentQueue.expirationTime,
first: currentQueue.first,
last: currentQueue.last,
isInitialized: currentQueue.isInitialized,
// These fields are no longer valid because they were already committed. Reset them.
callbackList: null,
hasForceUpdate: false,
};
}
// Reset the remaining expiration time. If we skip over any updates, we'll
// increase this accordingly.
queue.expirationTime = NoWork;
let dontMutatePrevState = true;
let update = queue.first;
let didSkip = false;
while (update !== null) {
const updateExpirationTime = update.expirationTime;
if (updateExpirationTime > renderExpirationTime) {
// 此更新优先级不够,不处理,跳过
if (queue.expirationTime === NoWork ||
queue.expirationTime > updateExpirationTime
) {
// 重新设置最近未处理更新的到期时间
queue.expirationTime = updateExpirationTime;
}
update = update.next;
continue;
}
// 优先级足够,处理
let partialState;
if (update.isReplace) {
// 使用replaceState()直接替换状态对象方式更新时
// 获取新状态对象
state = getStateFromUpdate(update, instance, state, props);
// 不需要合并至之前状态对象,标记为true
dontMutatePrevState = true;
} else {
// 更新部分状态方式
// 获取更新部分状态时的状态对象
partialState = getStateFromUpdate(update, instance, state, props);
if (partialState) {
if (dontMutatePrevState) {
// 上一次是替换状态,所以不能影响state
state = Object.assign({}, state, partialState);
} else {
// 更新部分状态,直接将新状态合并至上一次状态
state = Object.assign(state, partialState);
}
// 重置标记为false
dontMutatePrevState = false;
}
}
// 强制立即更新
if (update.isForced) {
queue.hasForceUpdate = true;
}
// 添加回调函数
if (update.callback !== null) {
// Append to list of callbacks.
let callbackList = queue.callbackList;
if (callbackList === null) {
callbackList = queue.callbackList = [];
}
callbackList.push(update);
}
// 遍历下一个更新任务
update = update.next;
}
// 返回最新的状态对象
return state;
}
Обновление
Планировщик координируется, и запланированные задачи в основном предназначены для выполнения обновлений компонента или дерева компонентов, и эти задачи выполняются средством обновления (Updater).Можно сказать, что планировщик контролирует весь уровень дерева компонентов приложения, в то время как средство обновления глубоко в каждом Более конкретная реализация каждого компонента внутри.
Когда создается экземпляр каждого компонента, вводится средство обновления, которое отвечает за координацию связи между компонентом и основным процессом React.Его обязанности можно резюмировать следующим образом:
- Найти экземпляр волокна, соответствующий экземпляру компонента;
- Запросить приоритет экземпляра волокна текущего компонента планировщика;
- поместить обновление в очередь обновлений волокна;
- Расписание задач обновления в соответствии с приоритетом;
Реализация апдейтера см.Модуль ReactFiberClassComponent:
export default function(
scheduleWork: (fiber: Fiber, expirationTime: ExpirationTime) => void,
computeExpirationForFiber: (fiber: Fiber) => ExpirationTime,
memoizeProps: (workInProgress: Fiber, props: any) => void,
memoizeState: (workInProgress: Fiber, state: any) => void,
) {
// Class component state updater
const updater = {
isMounted,
// 状态变更,更新入队列
enqueueSetState(instance, partialState, callback) {
// 获取fiber
const fiber = ReactInstanceMap.get(instance);
const expirationTime = computeExpirationForFiber(fiber);
// 创建更新任务
const update = {
expirationTime,
partialState,
callback,
isReplace: false,
isForced: false,
nextCallback: null,
next: null,
};
// 添加更新任务至fiber
insertUpdateIntoFiber(fiber, update);
// 调用调度器API以调度fiber任务
scheduleWork(fiber, expirationTime);
},
// 替换状态时
enqueueReplaceState(instance, state, callback) {
const fiber = ReactInstanceMap.get(instance);
const expirationTime = computeExpirationForFiber(fiber);
const update = {
expirationTime,
partialState: state,
callback,
isReplace: true,
isForced: false,
nextCallback: null,
next: null,
};
// 添加更新任务至fiber
insertUpdateIntoFiber(fiber, update);
scheduleWork(fiber, expirationTime);
},
// 强制更新
enqueueForceUpdate(instance, callback) {
const fiber = ReactInstanceMap.get(instance);
const expirationTime = computeExpirationForFiber(fiber);
const update = {
expirationTime,
partialState: null,
callback,
isReplace: false,
isForced: true,
nextCallback: null,
next: null,
};
insertUpdateIntoFiber(fiber, update);
scheduleWork(fiber, expirationTime);
},
};
// 调用组件实例生命周期方法并调用更新器API
function callComponentWillReceiveProps(
workInProgress, instance, newProps, newContext
) {
const oldState = instance.state;
instance.componentWillReceiveProps(newProps, newContext);
if (instance.state !== oldState) {
// 调用更新器入队列方法
updater.enqueueReplaceState(instance, instance.state, null);
}
}
// 设置Class组件实例的更新器和fiber
function adoptClassInstance(workInProgress, instance): {
// 设置更新器
instance.updater = updater;
workInProgress.stateNode = instance;
// 设置fiber
ReactInstanceMap.set(instance, workInProgress);
}
// 实例化Class组件实例
function constructClassInstance(workInProgress, props) {
const ctor = workInProgress.type;
const unmaskedContext = getUnmaskedContext(workInProgress);
const needsContext = isContextConsumer(workInProgress);
const context = needsContext
? getMaskedContext(workInProgress, unmaskedContext)
: emptyObject;
// 实例化组件类型
const instance = new ctor(props, context);
// 设置Class实例的更新器和fiber
adoptClassInstance(workInProgress, instance);
return instance;
}
// 挂载组件实例
function mountClassInstance(
workInProgress, renderExpirationTime) {
if (typeof instance.componentWillMount === 'function') {
callComponentWillMount(workInProgress, instance);
}
}
// 更新组件实例
function updateClassInstance(
current, workInProgress, renderExpirationTime
) {
// 组件实例
const instance = workInProgress.stateNode;
// 原Props或新Props
const oldProps = workInProgress.memoizedProps;
let newProps = workInProgress.pendingProps;
if (!newProps) {
// 没有新Props则直接使用原Props
newProps = oldProps;
}
if (typeof instance.componentWillReceiveProps === 'function' &&
(oldProps !== newProps)) {
// 调用方法进行更新器相关处理
callComponentWillReceiveProps(
workInProgress, instance, newProps
);
}
// 根据原状态对象和更新队列计算得到新状态对象
const oldState = workInProgress.memoizedState;
let newState;
if (workInProgress.updateQueue !== null) {
// 处理更新队列更新,计算得到新State对象
newState = processUpdateQueue(
current,
workInProgress,
workInProgress.updateQueue,
instance,
newProps,
renderExpirationTime,
);
} else {
newState = oldState;
}
// 检查是否需要更新组件
const shouldUpdate = checkShouldComponentUpdate(...);
if (shouldUpdate) {
if (typeof instance.componentWillUpdate === 'function') {
instance.componentWillUpdate(newProps, newState, newContext);
}
}
// 调用生命周期方法
...
return shouldUpdate;
}
return {
adoptClassInstance,
constructClassInstance,
mountClassInstance,
updateClassInstance
};
}
В основном реализовать следующие функции:
-
Инициализировать экземпляр компонента и настроить для него экземпляр волокна и средство обновления;
-
Инициализировать или обновить экземпляр компонента, вычислить новое состояние по очереди обновлений и т. д.;
-
Вызовите метод жизненного цикла экземпляра компонента и вызовите API средства обновления для обновления экземпляра волокна и т. д., например вызов для обновления экземпляра компонента.
callComponentWillReceiveProps
метод, который вызывает экземпляр компонентаcomponentWillReceiveProps
Методы жизненного цикла и вызов средства обновленияupdater.enqueueReplaceState
метод, обновите экземпляр волокна и добавьте обновление в очередь обновлений:// 调用组件实例生命周期方法并调用更新器API
function callComponentWillReceiveProps(
workInProgress, instance, newProps, newContext
) {
const oldState = instance.state;
instance.componentWillReceiveProps(newProps, newContext);
if (instance.state !== oldState) {
// 调用更新器入队列方法
updater.enqueueReplaceState(instance, instance.state, null);
}
}
Кроме того, важно обратить внимание наinsertUpdateIntoFiber
метод, который реализует добавление задачи обновления в экземпляр волокна компонента и внутренне обрабатывает добавление задачи в очередь обновления волокна См. исходный код, описанный в очереди обновления выше.Модуль ReactFiberUpdateQueue, в конце концов звонитinsertUpdateIntoQueue
.
Получить экземпляр волокна
Получить экземпляр волокна относительно просто.ReactInstanceMap
модульПредоставляемый API для обслуживания:
export function get(key) {
return key._reactInternalFiber;
}
export function set(key, value) {
key._reactInternalFiber = value;
}
используя узел_reactInternalFiber
Свойство поддерживает экземпляр волокна, вызываяget
метод можно получить.
получить приоритет
Приоритет экземпляра волокна контролируется планировщиком, поэтому вам нужно спросить планировщика о приоритете текущего экземпляра волокна, планировщик предоставляетcomputeExpirationForFiber
Получить приоритет конкретного экземпляра волокна, то есть получить время истечения (expirationTime) характерного экземпляра волокна Конкретную реализацию метода см. в главе Планировщик и приоритет.
Добавить задачу обновления в очередь обновлений
Когда состояние компонента изменяется, соответствующие задачи обновления компонента получают приоритет и помещаются в очередь обновления экземпляра волокна от высокого к низкому в соответствии с приоритетом, например, с использованиемsetState
Задача обновления, запускаемая этим методом, обычно добавляется в конец очереди обновлений.
После того, как планировщик завершит разделение задач на блоки задач, он будет использоватьperformUnitOfWork
Метод запускает обработку таск-юнита, а затем добавляет таск-юнит в очередь обновлений экземпляра файбера по приоритету, вызывая соответствующий API апдейтера компонента (см. выше реализацию):
-
Получите текущее устойчивое волокно из альтернативного свойства рабочего прогресса, а затем вызовите
beginWork
начать обработку обновления;// 处理任务单元
function performUnitOfWork(workInProgress: Fiber): Fiber | null {
// 当前最新版本fiber实例使用fiber的alternate属性获取
const current = workInProgress.alternate;
// 开始处理,返回子组件fiber实例
let next = beginWork(current, workInProgress, nextRenderExpirationTime);
if (next === null) {
// 不存在子级fiber,完成单元任务的处理,之后继续处理下一个任务
next = completeUnitOfWork(workInProgress);
}
return next;
}
-
beginWork
Возвращает экземпляр волокна подкомпонента входящего экземпляра волокна.Если он пустой, то это означает, что задача этого дерева компонентов выполнена, иначе будетworkLoop
Итеративные вызовы внутри методовperformUnitOfWork
Метод обработки:-
deadline
: да звонитеrequestIdleCallback
Объект времени кадра, возвращаемый, когда API выполняет обработчик задачи; -
nextUnitOfWork
: следующая единица задачи для обработки; -
shouldYield
: определить, следует ли приостановить текущий процесс обработки задачи;
function workLoop(expirationTime) {
// 渲染更新至DOM的到期时间值 小于 调度开始至开始处理此fiber的时间段值
// 说明任务已经过期
if (nextRenderExpirationTime <= mostRecentCurrentTime) {
// Flush all expired work, 处理所有已经到期的更新
while (nextUnitOfWork !== null) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
} else {
// Flush asynchronous work until the deadline runs out of time.
// 依次处理异步更新,直至deadline到达
while (nextUnitOfWork !== null && !shouldYield()) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
}
}
// 处理异步任务时, 调和器将询问渲染器是否暂停执行;
// 在DOM中,使用requestIdleCallback API实现
function shouldYield() {
if (deadline === null) {
return false;
}
if (deadline.timeRemaining() > 1) {
// 这一帧帧还有剩余时间,不需要暂停;
// 只有非过期任务可以到达此判断条件
return false;
}
deadlineDidExpire = true;
return true;
}
-
-
beginWork
В методе вызываются разные методы в зависимости от типа компонента, и в этих методах вызывается API средства обновления, чтобы добавить обновление в очередь обновлений. Конкретную реализацию см.Модуль ReactFiberBeginWork:// 引入更新器模块
import ReactFiberClassComponent from './ReactFiberClassComponent';
export default function(
config, hostContext, hydrationContext,
scheduleWork: (fiber: Fiber, expirationTime: ExpirationTime) => void,
computeExpirationForFiber: (fiber: Fiber) => ExpirationTime,
) {
// 初始化更新器模块,获取API
const {
adoptClassInstance, constructClassInstance,
mountClassInstance, updateClassInstance
} = ReactFiberClassComponent(
scheduleWork, computeExpirationForFiber,
memoizeProps, memoizeState
);
// beginWork,开始任务处理
function beginWork(
current, workInProgress, renderExpirationTime
) {
switch (workInProgress.tag) {
// 对应不同类型fiber,执行不同处理逻辑
case IndeterminateComponent:
...
case FunctionalComponent:
return updateFunctionalComponent(current, workInProgress);
case ClassComponent:
// 更新类组件,返回子级fiber实例
return updateClassComponent(
current, workInProgress, renderExpirationTime
);
case HostRoot:
return updateHostRoot(current, workInProgress, renderExpirationTime);
case HostComponent:
...
case HostText:
return updateHostText(current, workInProgress);
case CallHandlerPhase:
// This is a restart. Reset the tag to the initial phase.
workInProgress.tag = CallComponent;
case CallComponent:
...
case ReturnComponent:
// A return component is just a placeholder, we can just run through the
// next one immediately.
return null;
case HostPortal:
...
case Fragment:
return updateFragment(current, workInProgress);
default:;
}
}
return {
beginWork,
beginFailedWork
};
}-
вводить
ReactFiberClassComponent
Модули, связанные с обновлением, и инициализация для получения API; -
beginWork
В методе вызывается различная логическая обработка в соответствии с типом входящего незавершенного волокна (тегом); -
При логической обработке будет вызываться API периода обновления для добавления обновления в очередь обновлений;
-
от
ClassComponent
Например, вызовupdateClassComponent
метод:-
Определите, если это первый раз, инициализируйте и смонтируйте экземпляр компонента, в противном случае вызовите
updateClassInstance
метод обновления экземпляра компонента; -
последний звонок
finishClassComponent
метод, который согласовывает свои дочерние компоненты и возвращает экземпляр дочернего волокна;// 更新类组件
function updateClassComponent(
current, workInProgress, renderExpirationTime
) {
let shouldUpdate;
if (current === null) {
if (!workInProgress.stateNode) {
// fiber没有组件实例时需要初始化组件实例
constructClassInstance(workInProgress, workInProgress.pendingProps);
// 挂载组件实例
mountClassInstance(workInProgress, renderExpirationTime);
// 默认需要更新
shouldUpdate = true;
}
} else {
// 处理实例更新并返回是否需要更新组件
shouldUpdate = updateClassInstance(
current,
workInProgress,
renderExpirationTime,
);
}
// 更新完成后,返回子组件fiber实例
return finishClassComponent(
current, workInProgress, shouldUpdate, hasContext
);
}
// 类组件更新完成
function finishClassComponent(
current, workInProgress, shouldUpdate, hasContext
) {
if (!shouldUpdate) {
// 明确设置不需要更新时,不处理更新,
// 如shouldCOmponentUpdate方法return false
return bailoutOnAlreadyFinishedWork(current, workInProgress);
}
const instance = workInProgress.stateNode;
// 重新渲染
ReactCurrentOwner.current = workInProgress;
// 返回组件子组件树等内容
let nextChildren = instance.render();
// 调和子组件树,将迭代处理每一个组件
// 函数内将调用ReactChildFiber模块提供的API
reconcileChildren(current, workInProgress, nextChildren);
// 返回子组件fiber实例
return workInProgress.child;
}
-
-
Расписание задач обновления
В предыдущем разделе программа обновления могла добавлять обновления в очередь обновлений в соответствии с приоритетом, так как же запланировать задачу обновления?
Реализовано в обновленииМодуль ReactFiberClassComponentв, вenqueueSetState
,enqueueReplaceState
иenqueueForceUpdate
В методе очереди вызываются следующие методы:
insertUpdateIntoFiber(fiber, update);
scheduleWork(fiber, expirationTime);
-
insertUpdateIntoFiber
: добавьте обновление в экземпляр волокна, которое в конечном итоге будет добавлено в очередь обновлений; -
scheduleWork
: запланируйте задачу, передайте экземпляр волокна и время истечения срока действия задачи;
Рендеринг и согласование
На этапе согласования обработка DOM не задействована.После обработки обновления модуль рендеринга должен отобразить обновление в DOM.Это также концепция виртуального DOM (Virtual DOM) в приложениях React, то есть все обновления расчеты основаны на виртуальном DOM. Оптимизированное обновление отображается в реальном DOM только после завершения. Использование волокнаrequestIdleCallback
API более эффективно выполняет задачу рендеринга и обновления и реализует сегментацию задач.
Простой анализ исходного кода
В этом разделе кратко обсуждается взаимосвязь уровня кода между модулем рендеринга React и модулем алгоритма согласования.Если вам это не интересно, вы можете пропустить этот ограбление (раздел).
модуль рендеринга react-dom
В проекте, если вы хотите отобразить приложение на странице, у вас обычно есть следующий код:
import ReactDOM from 'react-dom';
import App form './App'; // 应用根组件
ReactDOM.render(
<App>,
document.querySelector('#App') // 应用挂载容器DOM
);
react-dom
Модуль — это схема рендеринга, подходящая для рендеринга приложений React на стороне браузера.Исходный код модуля ReactDOMСтруктура выглядит следующим образом:
const ReactDOM = {
render(
element: React$Element<any>, // React元素,通常是项目根组件
container: DOMContainer, // React应用挂载的DOM容器
callback: ?Function, // 回调函数
) {
return renderSubtreeIntoContainer(
null,
element,
container,
false,
callback,
);
}
};
Общие компоненты рендеринга в DOMrender
Метод такой же, как указано выше, вызовrenderSubtreeIntoContainer
метод, который отображает дерево подкомпонентов компонента:
// 渲染组件的子组件树至父容器
function renderSubtreeIntoContainer(
parentComponent: ?React$Component<any, any>,
children: ReactNodeList,
container: DOMContainer,
forceHydrate: boolean,
callback: ?Function,
) {
let root = container._reactRootContainer;
if (!root) {
// 初次渲染时初始化
// 创建react根容器
const newRoot = DOMRenderer.createContainer(container, shouldHydrate);
// 缓存react根容器至DOM容器的reactRootContainer属性
root = container._reactRootContainer = newRoot;
// 初始化容器相关
// Initial mount should not be batched.
DOMRenderer.unbatchedUpdates(() => {
DOMRenderer.updateContainer(children, newRoot, parentComponent, callback);
});
} else {
// 如果不是初次渲染则直接更新容器
DOMRenderer.updateContainer(children, root, parentComponent, callback);
}
// 返回根容器fiber树的根fiber实例
return DOMRenderer.getPublicRootInstance(root);
}
Объект визуализации DOM
DOMRenderer
Это объект рендеринга DOM, возвращаемый вызовом алгоритма согласования.Здесь будет передан API операции рендеринга пользовательского интерфейса модуля рендеринга, например:
// 调用调和算法方法
const DOMRenderer = ReactFiberReconciler(
// 传递至调和算法中的渲染UI(react-dom模块即DOM)
// 实际操作API
{
getPublicInstance(instance) {
return instance;
},
createInstance(
type: string,
props: Props,
rootContainerInstance: Container,
hostContext: HostContext,
internalInstanceHandle: Object,
) {
// 创建DOM元素
const domElement = createElement(
type,
props,
rootContainerInstance,
parentNamespace,
);
precacheFiberNode(internalInstanceHandle, domElement);
updateFiberProps(domElement, props);
return domElement;
},
now: ReactDOMFrameScheduling.now,
mutation: {
// 提交渲染
commitMount(
domElement: Instance,
type: string,
newProps: Props,
internalInstanceHandle: Object,
) {
((domElement: any):
| HTMLButtonElement
| HTMLInputElement
| HTMLSelectElement
| HTMLTextAreaElement).focus();
},
// 提交更新
commitUpdate(
domElement: Instance,
updatePayload: Array<mixed>,
type: string,
oldProps: Props,
newProps: Props,
internalInstanceHandle: Object,
) {
// 更新属性
updateFiberProps(domElement, newProps);
// 对DOM节点进行Diff算法分析
updateProperties(domElement, updatePayload, type, oldProps, newProps);
},
// 清空文本内容
resetTextContent(domElement: Instance): void {
domElement.textContent = '';
},
// 添加为子级
appendChild(
parentInstance: Instance,
child: Instance | TextInstance,
): void {
parentInstance.appendChild(child);
}
...
}
});
Исходный код ReactDOMFrameScheduling.now см. на Github.
будет выполнено, когда задача завершитсяcreateInstance
метод, затем вызовитеcreateElement
Элементы DOM создаются и добавляются в документ.
Вход алгоритма согласования
import ReactFiberScheduler from './ReactFiberScheduler';
import {insertUpdateIntoFiber} from './ReactFiberUpdateQueue';
export default function Reconciler(
// all parameters as config object
// 下文用到的config参数即从此处传入
getPublicInstance,
createInstance,
...
) {
// 生成调度器API
var {
computeAsyncExpiration, computeExpirationForFiber, scheduleWork,
batchedUpdates, unbatchedUpdates, flushSync, deferredUpdates,
} = ReactFiberScheduler(config);
return {
// 创建容器
createContainer(containerInfo, hydrate: boolean) {
// 创建根fiber实例
return createFiberRoot(containerInfo, hydrate);
},
// 更新容器内容
updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
callback: ?Function,
): void {
const current = container.current;
...
// 更新
scheduleTopLevelUpdate(current, element, callback);
},
...
// 获取容器fiber树的根fiber实例
getPublicRootInstance (container) {
// 获取fiber实例
const containerFiber = container.current;
if (!containerFiber.child) {
return null;
}
switch (containerFiber.child.tag) {
case HostComponent:
return getPublicInstance(containerFiber.child.stateNode);
default:
return containerFiber.child.stateNode;
}
},
unbatchedUpdates
}
}
существуетreact-dom
вызов модуля рендерингаcreateContainer
Создайте экземпляр контейнера и корневого волокна, объект FiberRoot, вызовитеupdateContainer
способ обновления содержимого контейнера.
Начать обновление
// 更新
function scheduleTopLevelUpdate(
current: Fiber,
element: ReactNodeList,
callback: ?Function,
) {
callback = callback === undefined ? null : callback;
const update = {
expirationTime,
partialState: {element},
callback,
isReplace: false,
isForced: false,
nextCallback: null,
next: null,
};
// 更新fiber实例
insertUpdateIntoFiber(current, update);
// 执行任务
scheduleWork(current, expirationTime);
}
обрабатывать обновления
перечислитьscheduleWork
Метод обрабатывает задачу обновления, см. выше реализацию,исходный код.
Отправить обновление
После обработки обновления необходимо подтвердить, что обновление отправлено в модуль рендеринга, а затем модуль рендеринга может отобразить обновление в DOM.
import ReactFiberCommitWork from './ReactFiberCommitWork';
const {
commitResetTextContent,
commitPlacement,
commitDeletion,
commitWork,
commitLifeCycles,
commitAttachRef,
commitDetachRef,
} = ReactFiberCommitWork(config, captureError);
function commitRoot(finishedWork) {
...
commitAllHostEffects();
}
// 循环执行提交更新
function commitAllHostEffects() {
while (nextEffect !== null) {
let primaryEffectTag =
effectTag & ~(Callback | Err | ContentReset | Ref | PerformedWork);
switch (primaryEffectTag) {
case Placement: {
commitPlacement(nextEffect);
nextEffect.effectTag &= ~Placement;
break;
}
case PlacementAndUpdate: {
// Placement
commitPlacement(nextEffect);
nextEffect.effectTag &= ~Placement;
// Update
const current = nextEffect.alternate;
commitWork(current, nextEffect);
break;
}
case Update: {
const current = nextEffect.alternate;
commitWork(current, nextEffect);
break;
}
case Deletion: {
isUnmounting = true;
commitDeletion(nextEffect);
isUnmounting = false;
break;
}
}
nextEffect = nextEffect.nextEffect;
}
}
// Flush sync work.
let finishedWork = root.finishedWork;
if (finishedWork !== null) {
// This root is already complete. We can commit it.
root.finishedWork = null;
root.remainingExpirationTime = commitRoot(finishedWork);
}
Отправить обновлениеЭто завершающий этап подтверждения компонента обновления, основная логика которого следующая:
export default function (mutation, ...) {
const {
commitMount,
commitUpdate,
resetTextContent,
commitTextUpdate,
appendChild,
appendChildToContainer,
insertBefore,
insertInContainerBefore,
removeChild,
removeChildFromContainer,
} = mutation;
function commitWork(current: Fiber | null, finishedWork: Fiber): void {
switch (finishedWork.tag) {
case ClassComponent: {
return;
}
case HostComponent: {
const instance: I = finishedWork.stateNode;
if (instance != null) {
// Commit the work prepared earlier.
const newProps = finishedWork.memoizedProps;
// For hydration we reuse the update path but we treat the oldProps
// as the newProps. The updatePayload will contain the real change in
// this case.
const oldProps = current !== null ? current.memoizedProps : newProps;
const type = finishedWork.type;
// TODO: Type the updateQueue to be specific to host components.
const updatePayload = finishedWork.updateQueue:;
finishedWork.updateQueue = null;
if (updatePayload !== null) {
commitUpdate(
instance,
updatePayload,
type,
oldProps,
newProps,
finishedWork,
);
}
}
return;
}
case HostText: {
const textInstance = finishedWork.stateNode;
const newText = finishedWork.memoizedProps;
// For hydration we reuse the update path but we treat the oldProps
// as the newProps. The updatePayload will contain the real change in
// this case.
const oldText: string =
current !== null ? current.memoizedProps : newText;
commitTextUpdate(textInstance, oldText, newText);
return;
}
case HostRoot: {
return;
}
default: {
}
}
}
}