нажмитеВойдите в репозиторий отладки исходного кода React.
В параллельном режиме React во время выполнения задач с низким приоритетом, как только поступает задача с более высоким приоритетом, задача с низким приоритетом будет отменена, а задача с высоким приоритетом будет выполняться первой. Когда задача с высоким приоритетом будет завершена, задача с низким приоритетом будет выполнена снова.
Давайте используем конкретный пример, чтобы понять организацию очереди задач с высоким приоритетом.
Есть такой компонент, состояние 0, при входе на страницу будет вызываться setState для увеличения состояния на 1, что расценивается как низкоприоритетная задача. React начинает обновляться, когда эта низкоприоритетная задача не выполнена, он имитирует нажатие кнопки и добавляет 2 к состоянию, которое используется как высокоприоритетная задача. Как видите, числа на странице меняются с 0 -> 2 -> 3, а не 0 -> 1 -> 3. Это означает, что пока выполняется задача с низким приоритетом (плюс 1), появляется задача с высоким приоритетом, и она устанавливает состояние на 2. Из-за того, что задача с высоким приоритетом прерывает очередь, задача с низким приоритетом с состоянием, установленным на 1, будет отменена, а задача с высоким приоритетом будет выполнена первой, поэтому число изменится с 0 на 2. После того, как задача с высоким приоритетом будет выполнена, задача с низким приоритетом будет выполнена заново, поэтому состояние увеличится с 2 до 3.
Феномен следующим образом:
Используя инструмент анализа производительности Chrome для захвата процесса обновления, вы можете четко увидеть процесс постановки в очередь приоритетов.
Я сохранил полный файл профиля и могу загрузить его в Chrome, чтобы просмотреть его в деталях:очередь с высоким приоритетом.json.
Вы можете просмотреть информацию о двух приоритетах задач в процессе планирования в этом процессе.
Нажмите, чтобы просмотретьФайл примера кода обрезки очереди с высоким приоритетом.
Нажмите, чтобы просмотретьПример файла кода для проблемы голодания задачи с низким приоритетом.
Далее давайте начнем с setState и обсудим сущность этого поведения сокращения очереди, которое включает в себя создание объектов обновления, инициирование планирования, рабочие циклы, сокращение очереди задач с высоким приоритетом, обработку объектов обновления и повторение задач с низким приоритетом.
генерировать обновление
Когда setState вызывается, это означает, что узел волокна, соответствующий компоненту, произвел обновление. setState фактически генерирует объект обновления, вызывает enqueueSetState и подключает объект обновления к связанному списку updateQueue узла волокна.
Component.prototype.setState = function(partialState, callback) {
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
Задача enqueueSetState — создать объект обновления, поместить его в список обновлений (updateQueue) узла волокна, а затем инициировать планирование.
enqueueSetState(inst, payload, callback) {
// 获取当前触发更新的fiber节点。inst是组件实例
const fiber = getInstance(inst);
// eventTime是当前触发更新的时间戳
const eventTime = requestEventTime();
const suspenseConfig = requestCurrentSuspenseConfig();
// 获取本次update的优先级
const lane = requestUpdateLane(fiber, suspenseConfig);
// 创建update对象
const update = createUpdate(eventTime, lane, suspenseConfig);
// payload就是setState的参数,回调函数或者是对象的形式。
// 处理更新时参与计算新状态的过程
update.payload = payload;
// 将update放入fiber的updateQueue
enqueueUpdate(fiber, update);
// 开始进行调度
scheduleUpdateOnFiber(fiber, lane, eventTime);
}
Чтобы разобраться на конкретные вещи, сделанные в EnqueeeutState:
найти волокно
Во-первых, получите узел волокна, соответствующий компоненту, генерирующему обновление, потому что сгенерированный объект обновления необходимо поместить в updateQueue узла волокна. Затем получите время генерации текущего обновления, что связано с проблемой зависания обновления, его мы пока рассматривать не будем, а следующий suspenseConfig можно сначала проигнорировать.
приоритет вычислений
После этого важнее вычислить текущую приоритетную полосу для ее обновления:
const lane = requestUpdateLane(fiber, suspenseConfig);
При расчете этого приоритета, как вы решаете, на основе чего рассчитывать? Это должно начаться с синтетических событий React.
Когда событие инициируется, механизм синтетических событий вызывает функцию runWithPriority в планировщике, чтобы отправить процесс реального события с приоритетом события, соответствующим интерактивному событию. runWithPriority преобразует приоритет события во внутренний приоритет планировщика и записывает его. При вызове requestUpdateLane для расчета полосы он получит приоритет в планировщике в качестве основы для расчета полосы.
Исходный код этой части находится вздесь
Создайте объект обновления и поставьте в очередь updateQueue
Согласно lane, eventTime и suspenseConfig, для создания объекта обновления структура выглядит следующим образом:
const update: Update<*> = {
eventTime,
lane,
suspenseConfig,
tag: UpdateState,
payload: null,
callback: null,
next: null,
};
- eventTime: время генерации обновления
- полоса: указывает приоритет
- suspenseConfig: задачи, связанные с приостановкой
- тег: указывает тип обновления (UpdateState, ReplaceState, ForceUpdate, CaptureUpdate)
- полезная нагрузка: состояние, переносимое обновлением.
- В компонентах класса есть две возможности: объекты ({}) и функции (prevstate, nextprops): newstate => {})
- В корневом компоненте это React.element, первый параметр ReactDOM.render.
- обратный вызов: можно понимать как обратный вызов setState
- next: указатель на следующее обновление
После повторного входа функция вызывает задачу React для выполнения:scheduleUpdateOnFiber
Запланировать выполнение задачи обновления.
Теперь мы знаем, что на узле волокна будет очередь обновлений, которая генерирует обновление, содержащее только что сгенерированное обновление. Следующее должно войтиscheduleUpdateOnFiber
Теперь приступайте к реальному процессу планирования. позвонивscheduleUpdateOnFiber
, задача построения дерева workInProgress на этапе рендеринга будет поставлена на выполнение, во время этого процесса будет обрабатываться updateQueue на файбере.
Подготовка расписания
Запись обновления ReactscheduleUpdateOnFiber
, который различает полосу обновления, шунтирует синхронное обновление и асинхронное обновление и позволяет им входить в соответствующие процессы. Но перед этим он выполнит еще несколько важных задач:
- Проверьте, является ли это бесконечным обновлением, например, setState вызывается в функции рендеринга.
- Начиная с узла, сгенерировавшего обновление, и заканчивая циклом до корня, цель состоит в том, чтобы собрать файбер.лейны вверх и собрать их в дочерние лейны родительского узла.
- Пометить обновление на корень, то есть поставить обновленный лейн в root.pendingLanes, приоритетный бенчмарк каждой отрисовки: renderLanes — самая актуальная часть лайнов, взятых из root.pendingLanes.
Эти три шага можно рассматривать как подготовку перед выполнением обновления.
Первый может предотвратить ситуацию, когда бесконечный цикл застревает.
Второй, если Fiber. В противном случае повторно используйте непосредственно существующее дерево волокна, не циклируют, а те волокнистые узлы, которые не должны быть обновлены, могут быть заблокированы.
Третье — добавить дорожку текущего объекта обновления в root.pendingLanes, чтобы гарантировать, что при фактическом запуске задачи обновления будет получена дорожка обновления, которая используется в качестве приоритета рендеринга (renderLanes) этого обновления для Обновить.
На самом деле, renderLanes, полученные при обновлении, не обязательно содержат полосу объекта обновления, потому что возможно, что это только обновление с более низким приоритетом, а перед ним может быть обновление с высоким приоритетом.
ЗаконченныйscheduleUpdateOnFiber
После общей логики давайте взглянем на его исходный код:
export function scheduleUpdateOnFiber(
fiber: Fiber,
lane: Lane,
eventTime: number,
) {
// 第一步,检查是否有无限更新
checkForNestedUpdates();
...
// 第二步,向上收集fiber.childLanes
const root = markUpdateLaneFromFiberToRoot(fiber, lane);
...
// 第三步,在root上标记更新,将update的lane放到root.pendingLanes
markRootUpdated(root, lane, eventTime);
...
// 根据Scheduler的优先级获取到对应的React优先级
const priorityLevel = getCurrentPriorityLevel();
if (lane === SyncLane) {
// 本次更新是同步的,例如传统的同步渲染模式
if (
(executionContext & LegacyUnbatchedContext) !== NoContext &&
(executionContext & (RenderContext | CommitContext)) === NoContext
) {
// 如果是本次更新是同步的,并且当前还未渲染,意味着主线程空闲,并没有React的
// 更新任务在执行,那么调用performSyncWorkOnRoot开始执行同步任务
...
performSyncWorkOnRoot(root);
} else {
// 如果是本次更新是同步的,不过当前有React更新任务正在进行,
// 而且因为无法打断,所以调用ensureRootIsScheduled
// 目的是去复用已经在更新的任务,让这个已有的任务
// 把这次更新顺便做了
ensureRootIsScheduled(root, eventTime);
...
}
} else {
...
// Schedule other updates after in case the callback is sync.
// 如果是更新是异步的,调用ensureRootIsScheduled去进入异步调度
ensureRootIsScheduled(root, eventTime);
schedulePendingInteractions(root, lane);
}
...
}
scheduleUpdateOnFiber
Полный исходный кодздесь, вот второй шаг:markUpdateLaneFromFiberToRootи третий шаг:markRootUpdatedПолный исходный код, я прокомментировал.
После предыдущих приготовленийscheduleUpdateOnFiber
в конце концов позвонитensureRootIsScheduled
, чтобы задача React была запланирована, это очень важная функция, она связана сКонвергенция равных или более низких задач,Высокоприоритетные задачи сокращаются в очередьа такжепроблема голодания задач, что подробно объясняется ниже.
начать планирование
в начале объяснитьensureRootIsScheduled
Прежде необходимо понять природу задачи обновления React.
Природа задач React
Генерация обновления в конечном итоге заставит React построить новое дерево волокон в памяти на основе существующего дерева волокон.Вычисление нового состояния, операция сравнения и некоторые вызовы жизненного цикла будут выполняться во время этого процесса построения. Эта общая строительная работа называется этапом рендеринга. Этап рендеринга представляет собой завершенную задачу обновления React в целом. Задачу обновления можно рассматривать как выполнение функции. В параллельном режиме эта функцияperformConcurrentWorkOnRoot
, планирование задачи обновления можно рассматривать так, как если бы эта функция планировалась планировщиком для выполнения в соответствии с приоритетом задачи.
Планирование планировщика и планирование React — это два совершенно разных понятия.Планирование React — это режим планирования, в котором планировщик координирует задачу. настоящее ядро механизма планирования. Реально выполнять задачу. Без него задача React не может быть выполнена, какой бы важной она ни была. Надеюсь, читатели смогут различить эти два понятия.
Когда задача запланирована, планировщик сгенерирует объект задачи (задачу), структура которого следующая, за исключением обратного вызова, вам пока не нужно заботиться о значении других полей.
var newTask = {
id: taskIdCounter++,
// 任务函数,也就是 performConcurrentWorkOnRoot
callback,
// 任务调度优先级,由即将讲到的任务优先级转化而来
priorityLevel,
// 任务开始执行的时间点
startTime,
// 任务的过期时间
expirationTime,
// 在小顶堆任务队列中排序的依据
sortIndex: -1,
};
Всякий раз, когда создается такая задача, она будет монтироваться на корневом узле.callbackNode
атрибут, чтобы указать, что в настоящее время запланирована задача, и приоритет задачи будет сохранен в корневомcallbackPriority
начальство,
Указывает, что при поступлении новой задачи ее приоритет задачи необходимо сравнить с приоритетом существующей задачи (root.callbackPriority), чтобы определить, необходимо ли отменять существующую задачу.
Поэтому при планировании задач приоритетность задач играет незаменимую и важную роль.
приоритет задачи
Сама задача генерируется обновлением, поэтому приоритет задачи по сути связан с приоритетом обновления, то есть update.lane (только связанный, не обязательно производный от него). Полученный приоритет задачи принадлежит lanePriority, это не полоса обновления, и это две концепции с приоритетом планирования внутри планировщика Отношение преобразования приоритета в React можно увидеть в статье, которую я резюмировал:Приоритет в реакции, здесь мы обсуждаем только процесс генерации приоритета задачи.
существуетПодготовка расписанияКак упоминалось в конце, update.lane будет помещен в root.pendingLanes, а затем дорожки с наивысшим приоритетом в root.pendingLanes будут получены как renderLanes. Генерация приоритета задачи происходит на этапе расчета renderLanes,Приоритет задачи на самом деле равен lanePriority, соответствующему renderLanes.. Поскольку renderLanes является эталоном приоритета этого обновления, соответствующий ему lanePriority используется в качестве приоритета задачи для измерения веса приоритета этой задачи обновления.
root.pendingLanes содержит дорожки всех ожидающих обновлений в текущем дереве волокон.
Существует три категории приоритетов задач:
- Приоритет синхронизации: приоритет задачи обновления, созданной в традиционном режиме синхронного рендеринга React.
- Приоритет пакета синхронизации: режим синхронизации в параллельный режим, режим перехода: режим блокировки (представлять) приоритет задачи обновления, созданной
- Приоритет в параллельном режиме: приоритет обновлений, созданных в параллельном режиме.
Две дорожки в крайнем правом углу — это приоритет синхронизации и пакетный приоритет синхронизации, а почти все оставшиеся дорожки слева относятся к параллельному режиму.
export const SyncLane: Lane = /* */ 0b0000000000000000000000000000001;
export const SyncBatchedLane: Lane = /* */ 0b0000000000000000000000000000010;
concurrent模式下的lanes:/* */ 0b1111111111111111111111111111100;
Функция для вычисления renderLanes:getNextLanes, функция, которая генерирует приоритет задачи,getHighestPriorityLanes
Приоритет задачи определяет, как задача планируется в React, а приоритет планирования задачи преобразуется из приоритета задачи (priorityLevel в структуре задач планировщика, приведенной выше). Определяет, когда Планировщик обрабатывает эту задачу.
Координация планирования задач — sureRootIsScheduled
До сих пор мы понимали природу задач и приоритетов задач, и теперь мы формально приступаем к процессу планирования задач. Планирование задач на стороне React в основном основано на приоритете задач для выполнения нескольких или отдельных задач.
В случае нескольких задач существующие задачи будутили повторно использовать, или отменитьОперация, в случае одиночной задачи, выполняется над задачейИли синхронная, или асинхронная, или пакетная синхронизация (не надо пока обращать внимание)решения по расписанию,
Такое поведение можно рассматривать как механизм координации планирования задач.ensureRootIsScheduled
достигать.
Давайте взглянемensureRootIsScheduled
Что делает функция, так это сначала подготавливает дорожки и приоритеты задач, необходимые для координации планирования этой задачи, а затем определяет, действительно ли необходимо планирование.
- Получить root.callbackNode, старую задачу
- Проверьте, не просрочена ли задача, поместите просроченную задачу в root.expiredLanes, цель состоит в том, чтобы позволить просроченной задаче войти в расписание с приоритетом синхронизации (выполнить немедленно)
- Получить renderLanes (желательно из root.expiredLanes), если renderLanes пусто, это означает, что планирование не требуется, просто верните
- Получить приоритет этой задачи, то есть новой задачи: newCallbackPriority
Далее идет процесс координации планирования задач:
- Сначала определите, нужно ли инициировать новое расписание, сравнив приоритет новой задачи с приоритетом старой задачи:
- Если они равны, это означает, что нет необходимости снова инициировать планирование, а старую задачу можно повторно использовать напрямую, чтобы старая задача могла выполнять новую задачу во время обработки обновления.
- Если они не равны, это означает, что приоритет новой задачи должен быть выше, чем у старой задачи.Высокоприоритетные задачи сокращаются в очередь, старую задачу нужно отменить.
- Истинный начальный график, см. приоритет задачи новой задачи:
- Приоритет синхронизации: вызов scheduleSyncCallback для синхронного выполнения задач.
- Синхронное пакетное выполнение: вызов scheduleCallback для добавления задач в расписание с немедленным приоритетом выполнения.
- Приоритет, принадлежащий параллельному режиму: вызов scheduleCallback, чтобы добавить задачу в расписание с новым приоритетом задачи, полученным выше.
Здесь следует отметить два момента:
- Почему, если приоритеты старой и новой задач не равны, новая задача должна иметь более высокий приоритет, чем старая задача?
Это связано с тем, что каждый раз, когда задача планируется получить приоритет задачи, получается только приоритет, соответствующий самой срочной части дорожек в root.pendingLanes, и приоритет, соответствующий полосе, удерживаемой низкоприоритетным обновлением. не может быть получено. Таким образом, несколько обновлений из одного и того же события могут быть объединены в одну задачу для выполнения.Подразумевается, что приоритеты нескольких обновлений, вызванных одним и тем же событием, одинаковы, и нет необходимости инициировать планирование нескольких задач. Например, вызов setState несколько раз в событии:
class Demo extends React.Component {
state = {
count: 0
}
onClick = () => {
this.setState({ count: 1 })
this.setState({ count: 2 })
}
render() {
return <button onClick={onClick}>{this.state.count}</button>
}
}
Непосредственно на странице будет отображаться значение 2. Хотя событие onClick вызывает setState дважды, оно вызовет только одно планирование. Планирование со счетчиком, установленным на 2, связано с тем, что приоритет такой же, как и приоритет задачи со счетчиком, установленным на 1. Таким образом, вместо повторного запуска планирования повторно используются существующие задачи. Это изменение в реализации React17 нескольких оптимизаций setState, которые ранее были реализованы с помощью механизма batchingUpdate.
- В чем разница между тремя режимами планирования приоритета задачи и каково их поведение?
- Приоритет синхронизации: традиционный режим синхронного рендеринга React и планирование просроченных задач. предоставлено Реактом
scheduleSyncCallback
функция будет функцией задачиperformSyncWorkOnRootОна добавляется в собственную очередь синхронизации React (syncQueue), а затем функция, циклически выполняющая syncQueue, добавляется в планировщик с приоритетом ImmediateSchedulerPriority, чтобы задача выполнялась в следующем цикле событий. Но из-за контроля React квант времени в этом режиме будет проверяться после выполнения всех задач, показывая, что квант времени отсутствует. - Синхронное пакетное выполнение: режим блокировки режима перехода из режима синхронного рендеринга в режим параллельного рендеринга перенесет функцию задачиperformSyncWorkOnRootДобавление в планировщик с приоритетом ImmediateSchedulerPriority также позволяет выполнять задачу в следующем цикле событий, и не будет производительности кванта времени.
- Приоритет, принадлежащий параллельному режиму: функция задачиperformConcurrentWorkOnRootЗадача добавляется в планировщик со своим собственным приоритетом.Внутренний планировщик будет управлять порядком задачи во внутренней очереди задач планировщика через этот приоритет, чтобы определить, что задача выполняется правильно, и задача будет иметь производительность временного интервала, когда он фактически выполняется.Реальная мощь планировщика асинхронного прерываемого планирования может быть проявлена.
Следует отметить, что приоритет, используемый для сравнения старой и новой задачи, не совпадает с приоритетом, переданным при добавлении задачи в планировщик здесь, последний может быть передан первым.lanePriorityToSchedulerPriorityтрансформировался.
После приведенного выше анализа я считаю, что у всех естьensureRootIsScheduled
Механизм работы относительно ясен, теперь давайте посмотрим на его реализацию:
function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
// 获取旧任务
const existingCallbackNode = root.callbackNode;
// 记录任务的过期时间,检查是否有过期任务,有则立即将它放到root.expiredLanes,
// 便于接下来将这个任务以同步模式立即调度
markStarvedLanesAsExpired(root, currentTime);
// 获取renderLanes
const nextLanes = getNextLanes(
root,
root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
);
// 获取renderLanes对应的任务优先级
const newCallbackPriority = returnNextLanesPriority();
if (nextLanes === NoLanes) {
// 如果渲染优先级为空,则不需要调度
if (existingCallbackNode !== null) {
cancelCallback(existingCallbackNode);
root.callbackNode = null;
root.callbackPriority = NoLanePriority;
}
return;
}
// 如果存在旧任务,那么看一下能否复用
if (existingCallbackNode !== null) {
// 获取旧任务的优先级
const existingCallbackPriority = root.callbackPriority;
// 如果新旧任务的优先级相同,则无需调度
if (existingCallbackPriority === newCallbackPriority) {
return;
}
// 代码执行到这里说明新任务的优先级高于旧任务的优先级
// 取消掉旧任务,实现高优先级任务插队
cancelCallback(existingCallbackNode);
}
// 调度一个新任务
let newCallbackNode;
if (newCallbackPriority === SyncLanePriority) {
// 若新任务的优先级为同步优先级,则同步调度,传统的同步渲染和过期任务会走这里
newCallbackNode = scheduleSyncCallback(
performSyncWorkOnRoot.bind(null, root),
);
} else if (newCallbackPriority === SyncBatchedLanePriority) {
// 同步模式到concurrent模式的过渡模式:blocking模式会走这里
newCallbackNode = scheduleCallback(
ImmediateSchedulerPriority,
performSyncWorkOnRoot.bind(null, root),
);
} else {
// concurrent模式的渲染会走这里
// 根据任务优先级获取Scheduler的调度优先级
const schedulerPriorityLevel = lanePriorityToSchedulerPriority(
newCallbackPriority,
);
// 计算出调度优先级之后,开始让Scheduler调度React的更新任务
newCallbackNode = scheduleCallback(
schedulerPriorityLevel,
performConcurrentWorkOnRoot.bind(null, root),
);
}
// 更新root上的任务优先级和任务,以便下次发起调度时候可以获取到
root.callbackPriority = newCallbackPriority;
root.callbackNode = newCallbackNode;
}
ensureRootIsScheduled
Фактически, он объединяет ключевую логику сокращения очереди задач с высоким приоритетом и голодания задач на уровне планирования задач.Это только решение на макроуровне.Причина решения заключается в том, что когда React обрабатывает обновления
Для выбора обновлений разного приоритета и операции маркировки на root.pendingLanes это требует от нас погружения в процесс выполнения задачи обновления.
обрабатывать обновления
После создания обновления объект обновления будет помещен в очередь обновлений и смонтирован на узле волокна. При построении дерева волокон для обработки updateQueue потребуется renderLanes.На этапе beginWork для компонентов класса
позвонюprocessUpdateQueue
Функция обрабатывает каждый объект обновления в связанном списке один за другим и вычисляет новое состояние.Если приоритет, удерживаемый обновлением, недостаточен, обработка обновления будет пропущена, а полоса пропущенного обновления будет помещена в волокнах .lanes, к счастью, собранных на этапе completeWork.
Процесс зацикливания updateQueue для вычисления состояния на самом деле сложнее, так как низкоприоритетное обновление будет пропущено и переделано, поэтому здесь возникает проблема унификации конечного состояния, принципиальная интерпретация этого процесса есть в моей статье. :Взгляните на принцип вычислительного состояния React., в этой статье сосредоточьтесь только на частях, связанных с приоритетом.
Часть о приоритете проще понять, то есть обрабатывать только обновления с достаточным приоритетом, пропускать обновления с недостаточным приоритетом и помещать дорожки этих обновлений в fiber.lanes. Перейдем непосредственно к реализации:
function processUpdateQueue<State>(
workInProgress: Fiber,
props: any,
instance: any,
renderLanes: Lanes,
): void {
...
if (firstBaseUpdate !== null) {
let update = firstBaseUpdate;
do {
const updateLane = update.lane;
// isSubsetOfLanes函数的意义是,判断当前更新的优先级(updateLane)
// 是否在渲染优先级(renderLanes)中如果不在,那么就说明优先级不足
if (!isSubsetOfLanes(renderLanes, updateLane)) {
...
/*
*
* newLanes会在最后被赋值到workInProgress.lanes上,而它又最终
* 会被收集到root.pendingLanes。
*
* 再次更新时会从root上的pendingLanes中找出应该在本次中更新的优先
* 级(renderLanes),renderLanes含有本次跳过的优先级,再次进入,
* processUpdateQueue wip的优先级符合要求,被更新掉,低优先级任务
* 因此被重做
* */
newLanes = mergeLanes(newLanes, updateLane);
} else {
// 优先级足够,去计算state
...
}
} while (true);
// 将newLanes赋值给workInProgress.lanes,
// 就是将被跳过的update的lane放到fiber.lanes
workInProgress.lanes = newLanes;
}
}
Только обновления обработки с достаточным приоритетом являются наиболее важными причинами выполнения высоких приоритетных задач. После зацикливания UPDATEUE один раз переулка из пропущенных обновлений помещается в Fiber.lanes. Теперь вам нужно только разместить его root.PendingLanes, что означает, что после этого раунда обновления есть еще задачи, которые не были обработаны, так что низкие приоритетные задачи могут быть перенесены. Таким образом, следующий процесс является фазой завершения узла волоконного волокна: фаза CompleteWork для сбора этих полос.
Соберите необработанные дорожки
Во время выполнения completeUnitOfWork, fiber.lanes и childLanes собираются слой за слоем в дочерние дорожки родительского волокна.Этот процесс происходит вcompleteUnitOfWork
функция называетсяresetChildLanes
, который зацикливает поддерево узла волокна, собирая дорожки и дочерние дорожки в дочернем узле, а также его одноуровневые элементы в дочерние дорожки на узле волокна, который в настоящее время находится в завершающей фазе.
Предположим, что в слое 3<List/>
а также<Table/>
Компоненты имеют обновления и пропускаются из-за недостаточного приоритета, поэтому, когда их родительский узел волокна div завершает UnitOfWork, он вызываетresetChildLanes
Соберите их дорожки в div fiber.childLanes и, наконец, соберите все дорожки в root.pendingLanes.
root(pendingLanes: 0b01110)
|
1 App
|
|
2 compeleteUnitOfWork-----------> div (childLanes: 0b01110)
/
/
3 <List/> ---------> <Table/> --------> p
(lanes: 0b00010) (lanes: 0b00100)
(childLanes: 0b01000) /
/ /
/ /
4 p ul
/
/
li ------> li
В каждом восходящем цикле вызывается функция resetChildLanes для сбора волокон fiber.childLanes слой за слоем.
function completeUnitOfWork(unitOfWork: Fiber): void {
// 已经结束beginWork阶段的fiber节点被称为completedWork
let completedWork = unitOfWork;
do {
// 向上一直循环到root的过程
...
// fiber节点的.flags上没有Incomplete,说明是正常完成了工作
if ((completedWork.flags & Incomplete) === NoFlags) {
...
// 调用resetChildLanes去收集lanes
resetChildLanes(completedWork);
...
} else {/*...*/}
...
} while (completedWork !== null);
...
}
В resetChildLanes собираются только дорожки и дочерние дорожки дочерних узлов и одноуровневых узлов завершаемого узла волокна:
function resetChildLanes(completedWork: Fiber) {
...
let newChildLanes = NoLanes;
if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) {
// profile相关,无需关注
} else {
// 循环子节点和兄弟节点,收集lanes
let child = completedWork.child;
while (child !== null) {
// 收集过程
newChildLanes = mergeLanes(
newChildLanes,
mergeLanes(child.lanes, child.childLanes),
);
child = child.sibling;
}
}
// 将收集到的lanes放到该fiber节点的childLanes中
completedWork.childLanes = newChildLanes;
}
Наконец, процесс помещения этих собранных дочерних полос в root.pendingLanes происходит на этапе фиксации этого обновления, поскольку приоритет рендеринга на этапе рендеринга исходит от root.pendingLanes и не может быть изменен произвольно. Поэтому его необходимо изменить на этапе фиксации после этапа рендеринга. Давайте посмотрим на реализацию этого процесса в commitRootImpl:
function commitRootImpl(root, renderPriorityLevel) {
// 将收集到的childLanes,连同root自己的lanes,一并赋值给remainingLanes
let remainingLanes = mergeLanes(finishedWork.lanes, finishedWork.childLanes);
// markRootFinished中会将remainingLanes赋值给remainingLanes
markRootFinished(root, remainingLanes);
...
}
перенести
На данный момент мы повторно собрали полосы задач с низким приоритетом в root.pendingLanes.На данный момент нам нужно только инициировать другое планирование.Позвонив снова на этапе фиксацииensureRootIsScheduled
Чтобы добиться того, чтобы вы прошли процесс планирования, выполняется задача с низким приоритетом.
function commitRootImpl(root, renderPriorityLevel) {
// 将收集到的childLanes,连同root自己的lanes,一并赋值给remainingLanes
let remainingLanes = mergeLanes(finishedWork.lanes, finishedWork.childLanes);
// markRootFinished中会将remainingLanes赋值给remainingLanes
markRootFinished(root, remainingLanes);
...
// 在每次所有更新完成的时候都会调用这个ensureRootIsScheduled
// 以保证root上任何的pendingLanes都能被处理
ensureRootIsScheduled(root, now());
}
Суммировать
Во всем процессе сокращения задач с высоким приоритетом и переделывания задач с низким приоритетом есть четыре ключевых момента:
- sureRootIsScheduled отменяет существующую задачу обновления с низким приоритетом, переназначает задачу для выполнения обновления с высоким приоритетом и использует наиболее важную часть дорожек в root.pendingLanes в качестве приоритета рендеринга.
- Пропускать низкоприоритетные обновления в updateQueue при выполнении задач обновления и отмечать их дорожки в fiber.lanes.
- Завершающая фаза узла волокна собирает волоконные дорожки в дочерние дорожки родительского волокна, вплоть до корня.
- На этапе фиксации все root.childLanes вместе с root.lanes назначаются root.pendingLanes.
- В конце фазы фиксации планирование повторно инициируется.
Весь процесс всегда фокусируется на высокоприоритетных задачах и принимает во внимание общую ситуацию, что может лучше всего отражать стремление React улучшить пользовательский опыт.