Я новичок, привыкший к Vue. На выходных я болтал с большим парнем, и он повернулся и заговорил о волокне, и большой парень его безумно презирал...
Босс также пожаловался мне на текущую среду забывания
- Baidu не вызывает доверия, а вещи которые Baidu прилетают рекламные и другие от того же автора (в большинстве случаев это правда)
- Очень много гидрологии выпускается в виде копии, а статья, которую вы видите, может быть устаревшей на несколько версий (в большинстве случаев это правда)
Итак, это блюдо запустило процесс чтения исходного кода, связанного с React Fiber. Зачем смотреть Файбер? Потому что Vue этого не делает, и Vue3 тоже, но он был потрясно взорван.
Это блюдо было написано по адресу:2020/05/25
, текущая версия исходного кода для справкиv16.13.1
Волокно, кажется, решает проблему?
Прежде всего, вы должны знать, почему появится Fiber.
Синхронное обновление старых версий React: Когда React решает загрузить или обновить дерево компонентов, он выполняет множество действий, таких как вызов функций жизненного цикла каждого компонента, вычисление и сравнение Virtual DOM и, наконец, обновление дерева DOM.
Например: обновление компонента занимает 1 миллисекунду, если вы хотите обновить 1000 компонентов, это займет время1秒
,В этот1秒
В процессе обновления основной поток предназначен для выполнения операции обновления.
Браузер перерисовывает текущую страницу через равные промежутки времени. Обычно эта частота составляет 60 раз в секунду. То есть каждые 16 миллисекунд (1/60 ≈ 0,0167) браузер будет выполнять периодическую перерисовку, которую мы называем кадром каждые 16 миллисекунд. Что делает браузер в этот период времени:
- Выполнить JS.
- Компьютерный стиль.
- Построить модель макета (Layout).
- Стиль слоя краски (Paint).
- Составной расчет отображает результат (Композитный).
Если какой-либо из этих шести шагов занимает слишком много времени, а общее время превышает 16 мс, пользователь может увидеть зависание. Обновление синхронизации компонентов в вышеперечисленных каштанах требует времени1秒
, что означает, что пользователь застрял почти на 1 секунду! ! ! (почти - -!)
Из-за характеристик однопоточного JavaScript каждая задача синхронизации не может занимать слишком много времени, или они не позволят программе соответственно, процесс обновления React виновен в этом табу на другие входы, в то время как React Fiber должен изменить статус-кво.
Что такое клетчатка
Одним из решений синхронных обновлений является разделение времени: фрагментируйте процесс обновления и разделите трудоемкую задачу на множество мелких частей. Выполняйте неблокирующий рендеринг, применяйте обновления в зависимости от приоритета и выполняйте предварительный рендеринг контента в фоновом режиме.
Волокно производитсяperformUnitOfWork
(ps: подробно описано позже) контролируемый методрабочая единица, как структура данных, используемая для представления определенных работников, другими словами, рабочей единицы, предоставляет удобный способ отслеживать, планировать, приостанавливать и прерывать работу через архитектуру Fiber.
Процесс создания и использования Fiber:
- Данные из каждого элемента React, возвращаемые методом рендеринга, объединяются в дерево узлов волокна.
- React создает узел волокна для каждого элемента React.
- В отличие от элементов React, волокно не воссоздается каждый раз при рендеринге.
- В последующих обновлениях React повторно использует узел волокна и обновляет необходимые свойства данными из соответствующего элемента React.
- В то же время React будет поддерживать
workInProgressTree
Используется для вычисления обновлений (двойная буферизация), которую можно рассматривать как дерево, представляющее текущий ход работы. Существует также старое дерево, представляющее отрендеренный интерфейс, React строит дерево WIP, сравнивая его со старым деревом.alternate
Указывает на одноранговый узел старого дерева.
ПС: как было сказано вышеworkInProgress
принадлежатьbeginWork
Процесс пропал, если захочешь записать, длина будет удвоена, так что подробно объяснять не буду... (в основном потому, что я ленивый и неуклюжий...)
Архитектура волокна делится на две основные фазы:reconciliation
(координация)/render 和 commit
,
Этап примирения React
На этапе Reconciliation после рефакторинга Fiber идея старой версии мало чем отличается, но она не будет рекурсивно сравниваться и не будет отправлена сразу.
Включает в себя спасательный крюк
- shouldComponentUpdate
-
componentWillMount(заброшенный) -
componentWillReceiveProps(заброшенный) -
componentWillUpdate(заброшенный) - static getDerivedStateFromProps
reconciliation
характеристика:
- 可以打断,在协调阶段如果时间片用完,React就会选择让出控制权。 Поскольку работа, выполняемая на этапе согласования, не приводит к каким-либо заметным для пользователя изменениям, на этом этапе нет проблем с передачей контроля.
- Поскольку этап координации может быть прерван, восстановлен или даже повторен, этап жизненного цикла хука координации React может вызываться несколько раз!Такой компонентWillMount может вызываться дважды.
- Таким образом, хук жизненного цикла на этапе координации не может содержать побочных эффектов, поэтому хук устарел.
Завершите процесс согласования. используется здесь深度优先搜索(DFS)
, сначала обработайте потомков, а затем братьев и сестер, пока цикл не завершится.
Этап React Commit
Включает в себя спасательный крюк
- componentDidMount
- componentDidUpdate
-
componentWillUnmount(заброшенный) - getSnapshotBeforeUpdate
render
а такжеcommit
: нельзя приостановить, интерфейс будет обновляться до завершения
Как Fiber обрабатывает приоритет?
Для пользовательского интерфейса необходимо рассмотреть следующие вопросы:
Не все обновления состояния нужно отображать сразу, например:
- Обновления скрытых частей. Не все приоритеты обновлений одинаковы.
- Ответы, введенные пользователем, имеют более высокий приоритет, чем ответы, заполненные контентом через запросы.
- В идеале для некоторых операций с высоким приоритетом должна быть возможность прерывать выполнение операций с низким приоритетом.
Итак, React определяет ряд приоритетов событий.
Ниже приведен исходный код приоритетного времени
[исходный файл](GitHub.com/Facebook/Горячие…
var maxSigned31BitInt = 1073741823;
// Times out immediately
var IMMEDIATE_PRIORITY_TIMEOUT = -1;
// Eventually times out
var USER_BLOCKING_PRIORITY = 250;
var NORMAL_PRIORITY_TIMEOUT = 5000;
var LOW_PRIORITY_TIMEOUT = 10000;
// Never times out
var IDLE_PRIORITY = maxSigned31BitInt;
Когда есть задача обновления, она не будет выполнять операцию Diff немедленно, а сначала отправит текущее обновление в очередь обновлений, а затем передаст егоScheduler
Чтобы справиться с этим, планировщик перейдет к процессу обновления на основе использования текущего основного потока.
Независимо от того, как процесс выполнения разделен и в каком порядке, Fiber обеспечит согласованность состояния и согласованность представления.
Как обеспечить, чтобы задачи с одинаковым приоритетом, запускаемые в течение определенного периода времени, имели одинаковое время истечения? Реагировать черезceiling
метод достижения. . . Это блюдо не использовалось|
грамматика...
Вот что обрабатывает время истечения срока действияceiling
исходный код
[исходный файл](GitHub.com/Facebook/Горячие…
function ceiling(num, precision) {
return (((num / precision) | 0) + 1) * precision;
}
Так почему же вам нужно гарантировать постоянство времени? Смотри ниже.
Как планируется волокно?
Сначала найдите адрес записи расписанияscheduleUpdateOnFiber
,
У каждого корня есть уникальная запланированная задача, и если она уже существует, мы хотим убедиться, что время истечения срока действия такое же, как и у задачи следующего уровня (поэтому используйте вышеупомянутоеceiling
Метод контролировать время истечения)
export function scheduleUpdateOnFiber(
fiber: Fiber,
expirationTime: ExpirationTime,
) {
checkForNestedUpdates();
warnAboutRenderPhaseUpdatesInDEV(fiber);
// 调用markUpdateTimeFromFiberToRoot,更新 fiber 节点的 expirationTime
// ps 此时的fiber树只有一个root fiber。
const root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);
if (root === null) {
warnAboutUpdateOnUnmountedFiberInDEV(fiber);
return;
}
// TODO: computeExpirationForFiber also reads the priority. Pass the
// priority as an argument to that function and this one.
// 还只是TODO
// computeExpirationForFiber还会读取优先级。
// 将优先级作为参数传递给该函数和该函数。
const priorityLevel = getCurrentPriorityLevel();
if (expirationTime === Sync) {
if (
// Check if we're inside unbatchedUpdates
// 检查是否在未批处理的更新内
(executionContext & LegacyUnbatchedContext) !== NoContext &&
// Check if we're not already rendering
// 检查是否尚未渲染
(executionContext & (RenderContext | CommitContext)) === NoContext
) {
// Register pending interactions on the root to avoid losing traced interaction data.
// 在根上注册待处理的交互,以避免丢失跟踪的交互数据。
schedulePendingInteractions(root, expirationTime);
// This is a legacy edge case. The initial mount of a ReactDOM.render-ed
// root inside of batchedUpdates should be synchronous, but layout updates
// should be deferred until the end of the batch.
performSyncWorkOnRoot(root);
} else {
ensureRootIsScheduled(root);
schedulePendingInteractions(root, expirationTime);
if (executionContext === NoContext) {
// Flush the synchronous work now, unless we're already working or inside
// a batch. This is intentionally inside scheduleUpdateOnFiber instead of
// scheduleCallbackForFiber to preserve the ability to schedule a callback
// without immediately flushing it. We only do this for user-initiated
// updates, to preserve historical behavior of legacy mode.
// 推入调度任务队列
flushSyncCallbackQueue();
}
}
} else {
// Schedule a discrete update but only if it's not Sync.
if (
(executionContext & DiscreteEventContext) !== NoContext &&
// Only updates at user-blocking priority or greater are considered
// discrete, even inside a discrete event.
(priorityLevel === UserBlockingPriority ||
priorityLevel === ImmediatePriority)
) {
// This is the result of a discrete event. Track the lowest priority
// discrete update per root so we can flush them early, if needed.
if (rootsWithPendingDiscreteUpdates === null) {
rootsWithPendingDiscreteUpdates = new Map([[root, expirationTime]]);
} else {
const lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);
if (
lastDiscreteTime === undefined ||
lastDiscreteTime > expirationTime
) {
rootsWithPendingDiscreteUpdates.set(root, expirationTime);
}
}
}
// Schedule other updates after in case the callback is sync.
ensureRootIsScheduled(root);
schedulePendingInteractions(root, expirationTime);
}
}
Приведенный выше исходный код в основном выполняет следующие действия.
- передача
markUpdateTimeFromFiberToRoot
Обновить волоконно-оптические узлыexpirationTime
-
ensureRootIsScheduled
(обновленный акцент) -
schedulePendingInteractions
на самом деле позвонитscheduleInteractions
-
scheduleInteractions
Будет использовать fiberroot.pendingInteractionMap
свойства и различныеexpirationTime
, получите набор задач обновления, необходимых для каждого расписания, запишите их количество и определите, не завершатся ли эти задачи ошибкой.
Суть обновления в томscheduleUpdateOnFiber
будет вызываться при каждом обновленииfunction ensureRootIsScheduled(root: FiberRoot)
НижеensureRootIsScheduled
исходный код
function ensureRootIsScheduled(root: FiberRoot) {
const lastExpiredTime = root.lastExpiredTime;
if (lastExpiredTime !== NoWork) {
// Special case: Expired work should flush synchronously.
root.callbackExpirationTime = Sync;
root.callbackPriority_old = ImmediatePriority;
root.callbackNode = scheduleSyncCallback(
performSyncWorkOnRoot.bind(null, root),
);
return;
}
const expirationTime = getNextRootExpirationTimeToWorkOn(root);
const existingCallbackNode = root.callbackNode;
if (expirationTime === NoWork) {
// There's nothing to work on.
if (existingCallbackNode !== null) {
root.callbackNode = null;
root.callbackExpirationTime = NoWork;
root.callbackPriority_old = NoPriority;
}
return;
}
// TODO: If this is an update, we already read the current time. Pass the
// time as an argument.
const currentTime = requestCurrentTimeForUpdate();
const priorityLevel = inferPriorityFromExpirationTime(
currentTime,
expirationTime,
);
// If there's an existing render task, confirm it has the correct priority and
// expiration time. Otherwise, we'll cancel it and schedule a new one.
if (existingCallbackNode !== null) {
const existingCallbackPriority = root.callbackPriority_old;
const existingCallbackExpirationTime = root.callbackExpirationTime;
if (
// Callback must have the exact same expiration time.
existingCallbackExpirationTime === expirationTime &&
// Callback must have greater or equal priority.
existingCallbackPriority >= priorityLevel
) {
// Existing callback is sufficient.
return;
}
// Need to schedule a new task.
// TODO: Instead of scheduling a new task, we should be able to change the
// priority of the existing one.
cancelCallback(existingCallbackNode);
}
root.callbackExpirationTime = expirationTime;
root.callbackPriority_old = priorityLevel;
let callbackNode;
if (expirationTime === Sync) {
// Sync React callbacks are scheduled on a special internal queue
callbackNode = scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
} else if (disableSchedulerTimeoutBasedOnReactExpirationTime) {
callbackNode = scheduleCallback(
priorityLevel,
performConcurrentWorkOnRoot.bind(null, root),
);
} else {
callbackNode = scheduleCallback(
priorityLevel,
performConcurrentWorkOnRoot.bind(null, root),
// Compute a task timeout based on the expiration time. This also affects
// ordering because tasks are processed in timeout order.
{timeout: expirationTimeToMs(expirationTime) - now()},
);
}
root.callbackNode = callbackNode;
}
выше исходный кодensureRootIsScheduled
В основном это выполнение различных функций push в зависимости от синхронного/асинхронного состояния.
Синхронное планированиеfunction scheduleSyncCallback(callback: SchedulerCallback)
:
- Если очередь не пуста, нажмите на синхронную очередь (
syncQueue.push(callback)
) - нажмите немедленно, если пустоочередь планирования задач(
Scheduler_scheduleCallback
) - буду
performSyncWorkOnRoot
так какSchedulerCallback
НижеscheduleSyncCallback
Исходный контент
export function scheduleSyncCallback(callback: SchedulerCallback) {
// Push this callback into an internal queue. We'll flush these either in
// the next tick, or earlier if something calls `flushSyncCallbackQueue`.
if (syncQueue === null) {
syncQueue = [callback];
// Flush the queue in the next tick, at the earliest.
immediateQueueCallbackNode = Scheduler_scheduleCallback(
Scheduler_ImmediatePriority,
flushSyncCallbackQueueImpl,
);
} else {
// Push onto existing queue. Don't need to schedule a callback because
// we already scheduled one when we created the queue.
syncQueue.push(callback);
}
return fakeCallbackNode;
}
Асинхронное планирование, планирование асинхронных задач очень простое, напрямую поместите асинхронную задачу в очередь планирования (Scheduler_scheduleCallback
), будуperformConcurrentWorkOnRoot
так какSchedulerCallback
export function scheduleCallback(
reactPriorityLevel: ReactPriorityLevel,
callback: SchedulerCallback,
options: SchedulerCallbackOptions | void | null,
) {
const priorityLevel = reactPriorityToSchedulerPriority(reactPriorityLevel);
return Scheduler_scheduleCallback(priorityLevel, callback, options);
}
Независимо от синхронного планирования или асинхронного планирования, он будет проходить черезScheduler_scheduleCallback
То есть основной метод планированияfunction unstable_scheduleCallback(priorityLevel, callback, options)
, у них будет свояSchedulerCallback
Совет: Поскольку многие из следующих кодов будут использоватьpeek
, вставить абзацpeek
Реализация, по сути, заключается в возврате первого в массиве или null
просматривать связанные исходные файлы
export function peek(heap: Heap): Node | null {
const first = heap[0];
return first === undefined ? null : first;
}
НижеScheduler_scheduleCallback
Связанный исходный код
[исходный файл](GitHub.com/Facebook/Горячие…
// 将一个任务推入任务调度队列
function unstable_scheduleCallback(priorityLevel, callback, options) {
var currentTime = getCurrentTime();
var startTime;
var timeout;
if (typeof options === 'object' && options !== null) {
var delay = options.delay;
if (typeof delay === 'number' && delay > 0) {
startTime = currentTime + delay;
} else {
startTime = currentTime;
}
timeout =
typeof options.timeout === 'number'
? options.timeout
: timeoutForPriorityLevel(priorityLevel);
} else {
// 针对不同的优先级算出不同的过期时间
timeout = timeoutForPriorityLevel(priorityLevel);
startTime = currentTime;
}
// 定义新的过期时间
var expirationTime = startTime + timeout;
// 定义一个新的任务
var newTask = {
id: taskIdCounter++,
callback,
priorityLevel,
startTime,
expirationTime,
sortIndex: -1,
};
if (enableProfiling) {
newTask.isQueued = false;
}
if (startTime > currentTime) {
// This is a delayed task.
newTask.sortIndex = startTime;
// 将超时的任务推入超时队列
push(timerQueue, newTask);
if (peek(taskQueue) === null && newTask === peek(timerQueue)) {
// All tasks are delayed, and this is the task with the earliest delay.
// 当所有任务都延迟时,而且该任务是最早的任务
if (isHostTimeoutScheduled) {
// Cancel an existing timeout.
cancelHostTimeout();
} else {
isHostTimeoutScheduled = true;
}
// Schedule a timeout.
requestHostTimeout(handleTimeout, startTime - currentTime);
}
} else {
newTask.sortIndex = expirationTime;
// 将新的任务推入任务队列
push(taskQueue, newTask);
if (enableProfiling) {
markTaskStart(newTask, currentTime);
newTask.isQueued = true;
}
// Schedule a host callback, if needed. If we're already performing work,
// wait until the next time we yield.
// 执行回调方法,如果已经再工作需要等待一次回调的完成
if (!isHostCallbackScheduled && !isPerformingWork) {
isHostCallbackScheduled = true;
(flushWork);
}
}
return newTask;
}
намекать:markTaskStart
В основном играют функцию записи, соответствующуюmarkTaskCompleted
export function markTaskStart(
task: {
id: number,
priorityLevel: PriorityLevel,
...
},
ms: number,
) {
if (enableProfiling) {
profilingState[QUEUE_SIZE]++;
if (eventLog !== null) {
// performance.now returns a float, representing milliseconds. When the
// event is logged, it's coerced to an int. Convert to microseconds to
// maintain extra degrees of precision.
logEvent([TaskStartEvent, ms * 1000, task.id, task.priorityLevel]);
}
}
}
export function markTaskCompleted(
task: {
id: number,
priorityLevel: PriorityLevel,
...
},
ms: number,
) {
if (enableProfiling) {
profilingState[PRIORITY] = NoPriority;
profilingState[CURRENT_TASK_ID] = 0;
profilingState[QUEUE_SIZE]--;
if (eventLog !== null) {
logEvent([TaskCompleteEvent, ms * 1000, task.id]);
}
}
}
unstable_scheduleCallback
В основном делать несколько вещей
- пройти через
options.delay
а такжеoptions.timeout
плюсtimeoutForPriorityLevel()
получитьnewTask
изexpirationTime
- Если срок действия задачи истек
- Вставьте задачу тайм-аута в очередь тайм-аута
- Если все задачи задерживаются, и эта задача является самой ранней задачей, она будет вызвана
cancelHostTimeout
- передача
requestHostTimeout
- Вставьте новую задачу в очередь задач
НаполнятьcancelHostTimeout
исходный код
cancelHostTimeout = function() {
clearTimeout(_timeoutID);
};
макияж, миритьсяrequestHostTimeout
исходный код
requestHostTimeout = function(cb, ms) {
_timeoutID = setTimeout(cb, ms);
};
потомrequestHostTimeout
изcb
то естьhandleTimeout
Что это такое?
function handleTimeout(currentTime) {
isHostTimeoutScheduled = false;
advanceTimers(currentTime);
if (!isHostCallbackScheduled) {
if (peek(taskQueue) !== null) {
isHostCallbackScheduled = true;
requestHostCallback(flushWork);
} else {
const firstTimer = peek(timerQueue);
if (firstTimer !== null) {
requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
}
}
}
}
Вышеупомянутый метод очень важен, он в основном делает следующие вещи
- передача
advanceTimers
Проверьте задачи, которые больше не задерживаются, и добавьте их в очередь.
НижеadvanceTimers
исходный код
function advanceTimers(currentTime) {
// Check for tasks that are no longer delayed and add them to the queue.
let timer = peek(timerQueue);
while (timer !== null) {
if (timer.callback === null) {
// Timer was cancelled.
pop(timerQueue);
} else if (timer.startTime <= currentTime) {
// Timer fired. Transfer to the task queue.
pop(timerQueue);
timer.sortIndex = timer.expirationTime;
push(taskQueue, timer);
if (enableProfiling) {
markTaskStart(timer, currentTime);
timer.isQueued = true;
}
} else {
// Remaining timers are pending.
return;
}
timer = peek(timerQueue);
}
}
- передача
requestHostCallback
пройти черезMessageChannel
Асинхронный метод включения планирования задачperformWorkUntilDeadline
requestHostCallback
Этот метод особенно важен
// 通过onmessage 调用 performWorkUntilDeadline 方法
channel.port1.onmessage = performWorkUntilDeadline;
// postMessage
requestHostCallback = function(callback) {
scheduledHostCallback = callback;
if (!isMessageLoopRunning) {
isMessageLoopRunning = true;
port.postMessage(null);
}
};
Затем тот же файлperformWorkUntilDeadline
, называетсяscheduledHostCallback
, то есть ранее пройденноеflushWork
const performWorkUntilDeadline = () => {
if (scheduledHostCallback !== null) {
const currentTime = getCurrentTime();
// Yield after `yieldInterval` ms, regardless of where we are in the vsync
// cycle. This means there's always time remaining at the beginning of
// the message event.
deadline = currentTime + yieldInterval;
const hasTimeRemaining = true;
try {
const hasMoreWork = scheduledHostCallback(
hasTimeRemaining,
currentTime,
);
if (!hasMoreWork) {
isMessageLoopRunning = false;
scheduledHostCallback = null;
} else {
// If there's more work, schedule the next message event at the end
// of the preceding one.
port.postMessage(null);
}
} catch (error) {
// If a scheduler task throws, exit the current browser task so the
// error can be observed.
port.postMessage(null);
throw error;
}
} else {
isMessageLoopRunning = false;
}
// Yielding to the browser will give it a chance to paint, so we can
// reset this.
needsPaint = false;
};
flushWork
Основная функция - вызовworkLoop
перебрать все задачи
function flushWork(hasTimeRemaining, initialTime) {
if (enableProfiling) {
markSchedulerUnsuspended(initialTime);
}
// We'll need a host callback the next time work is scheduled.
isHostCallbackScheduled = false;
if (isHostTimeoutScheduled) {
// We scheduled a timeout but it's no longer needed. Cancel it.
isHostTimeoutScheduled = false;
cancelHostTimeout();
}
isPerformingWork = true;
const previousPriorityLevel = currentPriorityLevel;
try {
if (enableProfiling) {
try {
return workLoop(hasTimeRemaining, initialTime);
} catch (error) {
if (currentTask !== null) {
const currentTime = getCurrentTime();
markTaskErrored(currentTask, currentTime);
currentTask.isQueued = false;
}
throw error;
}
} else {
// No catch in prod codepath.
return workLoop(hasTimeRemaining, initialTime);
}
} finally {
currentTask = null;
currentPriorityLevel = previousPriorityLevel;
isPerformingWork = false;
if (enableProfiling) {
const currentTime = getCurrentTime();
markSchedulerSuspended(currentTime);
}
}
}
workLoop
а такжеflushWork
В файле роль состоит в том, чтобы взять задачу с наивысшим приоритетом из очереди запланированных задач и выполнить ее.
Помните, что я сказал вышеSchedulerCallback
?
- Для синхронного выполнения задачи есть
performSyncWorkOnRoot
- Для асинхронного выполнения задачи есть
performConcurrentWorkOnRoot
function workLoop(hasTimeRemaining, initialTime) {
let currentTime = initialTime;
advanceTimers(currentTime);
currentTask = peek(taskQueue);
while (
currentTask !== null &&
!(enableSchedulerDebugging && isSchedulerPaused)
) {
if (
currentTask.expirationTime > currentTime &&
(!hasTimeRemaining || shouldYieldToHost())
) {
// This currentTask hasn't expired, and we've reached the deadline.
break;
}
const callback = currentTask.callback;
if (callback !== null) {
currentTask.callback = null;
currentPriorityLevel = currentTask.priorityLevel;
const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;
markTaskRun(currentTask, currentTime);
const continuationCallback = callback(didUserCallbackTimeout);
currentTime = getCurrentTime();
if (typeof continuationCallback === 'function') {
currentTask.callback = continuationCallback;
markTaskYield(currentTask, currentTime);
} else {
if (enableProfiling) {
markTaskCompleted(currentTask, currentTime);
currentTask.isQueued = false;
}
if (currentTask === peek(taskQueue)) {
pop(taskQueue);
}
}
advanceTimers(currentTime);
} else {
pop(taskQueue);
}
currentTask = peek(taskQueue);
}
// Return whether there's additional work
if (currentTask !== null) {
return true;
} else {
const firstTimer = peek(timerQueue);
if (firstTimer !== null) {
requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
}
return false;
}
}
в конце концов пройдетperformUnitOfWork
работать.
Этот метод является просто асинхронным методом, который можно прервать, и мы должны проверять, истекает ли время ожидания каждый раз, когда мы его вызываем.
function performUnitOfWork(unitOfWork: Fiber): void {
// The current, flushed, state of this fiber is the alternate. Ideally
// nothing should rely on this, but relying on it here means that we don't
// need an additional field on the work in progress.
const current = unitOfWork.alternate;
setCurrentDebugFiberInDEV(unitOfWork);
let next;
if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
startProfilerTimer(unitOfWork);
next = beginWork(current, unitOfWork, renderExpirationTime);
stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
} else {
next = beginWork(current, unitOfWork, renderExpirationTime);
}
resetCurrentDebugFiberInDEV();
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
// If this doesn't spawn new work, complete the current work.
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
ReactCurrentOwner.current = null;
}
надstartProfilerTimer
а такжеstopProfilerTimerIfRunningAndRecordDelta
По сути, это регистрация времени работы волокна.
function startProfilerTimer(fiber: Fiber): void {
if (!enableProfilerTimer) {
return;
}
profilerStartTime = now();
if (((fiber.actualStartTime: any): number) < 0) {
fiber.actualStartTime = now();
}
}
function stopProfilerTimerIfRunningAndRecordDelta(
fiber: Fiber,
overrideBaseTime: boolean,
): void {
if (!enableProfilerTimer) {
return;
}
if (profilerStartTime >= 0) {
const elapsedTime = now() - profilerStartTime;
fiber.actualDuration += elapsedTime;
if (overrideBaseTime) {
fiber.selfBaseDuration = elapsedTime;
}
profilerStartTime = -1;
}
}
Наконец, здесь приходитbeginWork
Обработать - -. Что там?workInProgress
И многоswitch case
.
Хочу увидетьbeginWork
Вы можете сами попробовать исходный кодИсходные файлы, связанные с beginWork
Суммировать
Последняя часть - резюме.Я долго думал, стоит ли писать это.У каждого читателя должно быть разное восприятие чтения исходников в разное время и в разном настроении (конечно, он тоже читатель, когда он пересматривает). Каждый взгляд должен иметь резюме каждого периода.
Но если вы не пишете резюме, этот анализ кажется скучным и неубедительным. Так что просто пропустите его (он должен быть оригинальным, а не где-либо еще)
- Fiber на самом деле является узлом, который представляет собой форму обхода связанного списка.
- Волокно рассчитывается по приоритету
expirationTime
получить срок годности - Благодаря структуре связанного списка разделение времени можно легко прервать и возобновить.
- Разделение времени осуществляется
settimeout
+postMessage
осуществленный - Выполняется, когда все задачи задерживаются
clearTimeout
- Расчет количества задач и рабочего времени
Почему Fiber использует связанные списки?
Использование структуры связанного списка — это только результат, а не цель.Первоначальная цель разработчиков React состояла в том, чтобы смоделировать стек вызовов.
Стек вызовов чаще всего используется для хранения адреса возврата подпрограммы. При вызове любой подпрограммы основная программа должна временно хранить адрес, по которому подпрограмма должна вернуться после выполнения. Следовательно, если вызываемая подпрограмма также вызывает другие подпрограммы, ее собственный адрес возврата должен храниться в стеке вызовов, а затем извлекаться после ее собственного выполнения. В дополнение к обратному адресу также сохраняются локальные переменные, параметры функций и передачи среды.
Поэтому объект Fiber разработан как структура связанного списка, а связанный список состоит из следующих основных свойств.
-
type
Типы -
return
Сохраняет родительский узел текущего узла -
child
сохранить первый дочерний узел -
sibling
Право первого брата узла хранения -
alternate
одноранговый узел старого дерева
Когда мы просматриваем diff дерева dom, даже если он прерван, нам нужно только помнить узел в момент прерывания, и мы можем возобновить обход и diff в следующем временном интервале. Это одно из больших преимуществ выбора связанных списков для волоконных структур данных.
Почему бы не использовать requestIdleCallback для разделения времени
События, выполняемые браузером каждый цикл
1. 宏任务
2. 微任务
4. requestAnimationFrame
5. IntersectionObserver
6. 更新界面
7. requestIdleCallback
8. 下一帧
согласно софициальныйописывать:
window.requestIdleCallback()
Метод ставит в очередь функции, которые вызываются в период простоя браузера. Это позволяет разработчикам выполнять фоновую и низкоприоритетную работу в основном цикле событий, не влияя на критичные к задержке события, такие как анимация и ответы ввода. Функции обычно выполняются в порядке первого вызова, однако, если функция обратного вызова указывает время ожидания выполнения.timeout
, можно перетасовать порядок выполнения, чтобы выполнить функцию до истечения времени ожидания. Вы можете вызвать функцию обратного вызова бездействияrequestIdleCallback()
, так что другой обратный вызов отправляется перед следующим проходом через цикл обработки событий.
Кажется, это идеально соответствует идее разделения времени, поэтому сначала рендеринг разделения времени React хотел использовать этот API, но текущая поддержка браузеров не сильна, иrequestIdleCallback
Слишком строгий и не выполняется достаточно часто, чтобы добиться плавного рендеринга пользовательского интерфейса.
И мы надеемся, что благодаря архитектуре Fiber,reconcilation
Процесс становится прерываемым. Отказаться от прав на выполнение ЦП «вовремя». Поэтому команде React пришлось реализовать собственную версию.
На самом деле идея Файбера согласуется с концепцией сопрограмм. Возьмите каштан:
Обычные функции: (нельзя прервать и возобновить)
const tasks = []
function run() {
let task
while (task = tasks.shift()) {
execute(task)
}
}
При использованииGenerator
грамматика:
const tasks = []
function * run() {
let task
while (task = tasks.shift()) {
// 判断是否有高优先级事件需要处理, 有的话让出控制权
if (hasHighPriorityEvent()) {
yield
}
// 处理完高优先级事件后,恢复函数调用栈,继续执行...
execute(task)
}
}
Но React попытался реализовать это с помощью Generator, но позже счел это очень хлопотным и сдался.
Почему при нарезке времени не используется Генератор
В основном по 2 причинам:
-
Generator
Каждая функция должна быть заключена в стек генератора. Это не только добавляет много накладных расходов на синтаксис, но также увеличивает накладные расходы во время выполнения в существующих реализациях. Хотя что-то лучше, чем ничего, проблемы с производительностью все еще существуют. - Основная причина в том, что генераторы имеют состояние. На полпути восстановиться невозможно. Если вы хотите восстановить сцену рекурсии, вам может потребоваться начать с самого начала, восстанавливая предыдущий стек вызовов.
Почему бы не использовать Web Workers для разделения времени
Можно ли пройтиWeb Worker
Создать многопоточную среду для разделения времени?
Команда React тоже думала об этом, пыталась придумать общие неизменяемые постоянные структуры данных, пробовала настраивать виртуальные машины и т. д., ноJavaScript
Этот язык к этому не относится.
Из-за изменчивых общих сред выполнения (например, прототипов) экосистема не готова, потому что вам нужно повторять загрузку кода и инициализацию модуля для всех рабочих процессов. Если сборщики мусора должны быть потокобезопасными, они не так эффективны, как в настоящее время, и разработчики виртуальных машин, похоже, не хотят брать на себя затраты на внедрение постоянных структур данных. Общие массивы с изменяемым типом, похоже, развиваются, но в современной экосистеме требование, чтобы все данные проходили через этот уровень, кажется неосуществимым. Искусственные границы между различными частями кодовой базы также не работают и создают ненужные трения. Даже в этом случае у вас остается много JS-кода (например, служебных библиотек), который необходимо копировать между рабочими процессами. Это приводит к замедлению времени запуска и накладным расходам памяти. Так что да, многопоточность может быть невозможна, пока мы не нацелимся на такие вещи, как Web Assembly.
Вы не можете безопасно прервать фоновый поток. Прерывание и перезапуск потоков не очень дешево. Во многих языках это также небезопасно, потому что вы можете быть в процессе какой-то ленивой инициализации. Даже если он эффективно прерван, вы должны продолжать тратить на него циклы ЦП.
Другое ограничение заключается в том, что невозможно определить, работают ли два потока над одним и тем же компонентом одновременно, поскольку поток нельзя немедленно прервать. Это приводит к некоторым ограничениям, таким как невозможность поддержки экземпляров класса с отслеживанием состояния (например, React.Component). Потоки не могут просто запомнить часть работы, которую вы делаете в одном потоке, и повторно использовать ее в другом потоке.
ps: я не знаю, как использовать React в этом блюде.Я впервые читаю исходный код React.Пожалуйста, поправьте меня, если я неправильно прочитал исходный код.
наконец
- Ставьте лайк, если считаете это полезным
- Содержание этой статьи взято изGitHub.com/Китай-США…
- Добро пожаловать, чтобы обратить внимание на общедоступную учетную запись «Продвинутый курс по интерфейсу», чтобы серьезно изучить интерфейс и продвигаться вместе. Отвечать
全栈
илиVue
Есть подарок для вас