Анализ исходного кода React: процесс рендеринга (2)

внешний интерфейс React.js

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

Информация, связанная со статьей

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

пожалуйста, откройте сейчасмой кодИ найдите файл ReactDOM.js в src в папке react-dom. Сегодняшний контент начнется здесь.

ReactRoot.prototype.render

В предыдущей статье мы рассмотрели, когдаReactDom.renderПри выполнении внутренний сначала определит, существует ли он ужеroot, если нет, он создаст одинroot. В сегодняшней статье мы узнаем, что существуетrootчто будет потом.

Сначала вы можете найти строку 592 кода.

Как видите, вышеприведенный код вызываетunbatchedUpdatesФункция, знания, связанные с этой функцией, на самом деле очень важны в React.

Все знают, сколькоsetStateВыполненные вместе, не запускают несколько рендеров React.

// 虽然 age 会变成 3,但不会触发 3 次渲染
this.setState({ age: 1 })
this.setState({ age: 2 })
this.setState({ age: 3 })

Это потому, что внутренне это три разаsetStateОптимизация — это обновление, термин — BatchUpdate, и мы также можем увидеть, как внутреннее обновление обрабатывается в последующем контенте.

Для рута нет необходимости в пакетном обновлении, так что вот вызовunbatchedUpdatesфункция, сообщающая внутренним компонентам, что пакетные обновления не требуются.

затем вunbatchedUpdatesОбратный вызов внутренне оценивает, существует ли онparentComponent. На этом шаге можно предположить, что не будетparentComponent, потому что мало кто будетrootвнешний плюсcontextкомпоненты. не существуетparentComponentбудет выполнятьroot.render(children, callback),здесьrenderОтноситсяReactRoot.prototype.render.

существуетrenderСначала мы удалим внутреннюю функциюroot,здесьrootОтносится к FiberRoot, если вы хотите узнать о FiberRoot, вы можете прочитатьпредыдущий пост. затем создалReactWorkЭкземпляр , нам не нужно вникать в этот контент, функция состоит в том, чтобы передать все входящие компоненты после того, как компонент будет отрисован или обновлен.ReactDom.renderВсе функции обратного вызова выполняются один раз.

Далее мы смотримupdateContainerКак внутри.

Начнем с FiberRootcurrentОн берет свой объект волокна из его свойств, а затем вычисляет два раза. Эти два времени очень важны в React, поэтому их нужно изучить в отдельном разделе.

время

прежде всегоcurrentTime,существуетrequestCurrentTimeОсновная функция внутреннего времени вычисления функцииrecomputeCurrentRendererTime.

function recomputeCurrentRendererTime() {
  const currentTimeMs = now() - originalStartTimeMs;
  currentRendererTime = msToExpirationTime(currentTimeMs);
}

now()то естьperformance.now(), если вы не знаете этот API, вы можете прочитать егоСвязанная документация,originalStartTimeMs— это переменная, которая генерируется при инициализации приложения React, и значение такжеperformance.now(), и это значение не будет изменено в дальнейшем. Затем, после вычитания этих двух значений, получается, сколько времени прошло с момента инициализации приложения React.

Затем нам нужно снова вычислить вычисленное значение по формуле, здесь| 0Функция состоит в том, чтобы взять целое число, то есть11 / 10 | 0 = 1

Далее предположим какие-то значения переменных, и всем будет легче понять, если их подставить в формулу.

еслиoriginalStartTimeMsдля2500, текущее время5000, то расчетная разница равна2500, то есть с момента инициализации приложения React прошло 2500 миллисекунд, и конечный результат, полученный по формуле, таков:

currentTime = 1073741822 - ((2500 / 10) | 0) = 1073741572

Далее идет расчетexpirationTime,Это время связано с приоритетом, чем больше значение, тем выше приоритет. И синхронизация является наивысшим приоритетом, ее значение равно1073741823, константа, которую мы видели ранееMAGIC_NUMBER_OFFSETплюс один.

существуетcomputeExpirationForFiberЕсть много функций филиала, но основной расчет только три строки кода, а именно:

// 同步
expirationTime = Sync
// 交互事件,优先级较高
expirationTime = computeInteractiveExpiration(currentTime)
// 异步,优先级较低
expirationTime = computeAsyncExpiration(currentTime)

Далее будем анализироватьcomputeInteractiveExpirationКак вычисляется время внутри функции, конечноcomputeAsyncExpirationТочно так же вычисляется время, за исключением того, что заменяются две переменные.

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

time = 1073741822 - ((((1073741822 - 1073741572 + 15) / 10) | 0) + 1) * 10 = 1073741552

Также вceilingв функции1 * bucketSizeMs / UNIT_SIZEЭто необходимо для сглаживания разницы во времени в течение определенного периода времени. Независимо от того, сколько задач необходимо выполнить в течение сглаженной разницы во времени, их время истечения одинаково. Это также оптимизация производительности и помогает регулировать поведение страницы рендеринга. .

В итоге мы вычислилиexpirationTimeМожно сделать вывод в другой раз:

export function expirationTimeToMs(expirationTime: ExpirationTime): number {
  return (MAGIC_NUMBER_OFFSET - expirationTime) * UNIT_SIZE;
}

Если взять ранее рассчитанноеexpirationTimeПодставив приведенный выше код, получим следующий результат:

(1073741822 - 1073741552) * 10 = 2700

Это время фактически совпадает с тем, что мы рассчитали выше.2500Разница в миллисекундах близка. потому чтоexpirationTimeОтносится к времени истечения задачи. React вычисляет крайний срок выполнения задачи в соответствии с приоритетом задачи и текущим временем. Пока это значение больше текущего времени, React всегда может отложить выполнение этой задачи, чтобы разрешить выполнение задач с более высоким приоритетом, но как только крайний срок задачи прошел, задача должна быть выполнена немедленно.

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

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

scheduleRootUpdate

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

Сначала мы создадим одинupdate,этот объект иsetStateтесно связанный

// update 对象的内部属性
expirationTime: expirationTime,
tag: UpdateState,
// setState 的第一二个参数
payload: null,
callback: null,
// 用于在队列中找到下一个节点
next: null,
nextEffect: null,

дляupdateЧто касается свойств внутри объекта, нам нужно сосредоточиться на следующем:nextАтрибуты. потому чтоupdateПо сути, это узел в очереди, по этому атрибуту можно найти следующий.update. Для пакетного обновления мы можем создать большеupdate, поэтому нам нужно преобразовать этиupdateОбъединены и сохранены, при необходимости извлекаются для обновленийstate.

существуетrenderПроцесс на самом деле является операцией обновления, но мы неsetState, так поставьpayloadназначить как{element}.

Далее мы будемcallbackназначить наupdateсвойства, здесьcallbackещеReactDom.renderтретий параметр.

Тогда мы просто создадимupdateобъекты вставляются в очередь,enqueueUpdateВнутренних веток у функции много и код простой.Код здесь больше не выкладывается.Кому интересно,могут прочитать сами. Основная функция функции состоит в том, чтобы создать или получить очередь, а затем поместитьupdateобъект поставлен в очередь.

последний звонокscheduleWorkФункция, вот контент, связанный с планированием, который мы подробно проанализируем в следующей статье.

Суммировать

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

наконец

Чтение исходного кода — очень утомительный процесс, но и польза от него огромна. Если у вас возникнут какие-либо вопросы в процессе чтения, вы можете связаться со мной в области комментариев.

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

Следующая статья связана с процессом рендеринга.

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