«Перевод» React Fiber Эти вещи: глубокое погружение в новый алгоритм координации

React.js
«Перевод» React Fiber Эти вещи: глубокое погружение в новый алгоритм координации

Переведено с:Inside Fiber: in-depth overview of the new reconciliation algorithm in React

React — это библиотека JavaScript для создания пользовательского интерфейса, ее ядромеханизмОн предназначен для отслеживания изменений состояния компонента и сопоставления обновленного состояния с новым интерфейсом. В React мы называем этот процесскоординация. мы называемsetStateметод для изменения состояния, а сам фреймворк проверяет, изменилось ли состояние или свойства, чтобы решить, нужно ли повторно отображать компонент.

Пара официальной документации Reactкоординационный механизмХорошо описано абстрактно: элементы React, жизненный цикл,renderКомбинированный эффект метода и алгоритма сравнения, применяемого к дочерним элементам компонента, — это координация. отrenderНеизменяемый элемент React, возвращаемый методом, часто называют «виртуальным DOM». Этот термин помог объяснить React людям на раннем этапе, но он также вызвал путаницу и больше не используется в документации React. В этой статье я буду называть его деревом элементов React.

В дополнение к дереву элементов React фреймворк всегда поддерживает внутренний экземпляр для хранения состояния (например, компоненты, узлы DOM и т. д.). Начиная с версии 16, React представил новый способ реализации внутреннего дерева экземпляров, который называетсяFiberалгоритм. Если вы хотите понять преимущества архитектуры Fiber, вы можете прочитатьКак и почему React использует связанные списки в Fiber.

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

Я принесу вам очень продвинутые знания 🧙. Я призываю вас прочитать, чтобы понять магию внутренней работы Concurrent React. Эта серия также станет для вас отличным руководством, если вы планируете начать писать код для React. яТвердо верящие в реверс-инжинирингПоэтому в этой статье 16.6.0 будет много ссылок на последнюю версию исходного кода.

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

я здесьag-GridВыступайте в роли защитника разработчиков. Если вы хотите узнать о сетках данных или ищете идеальное решение для сетки данных React, свяжитесь с нами или воспользуйтесь руководством.Начните работу с React Grid за 5 минут". Я буду рад ответить на любые ваши вопросы.

Введение

Ниже приведено простое приложение, которое я буду использовать на протяжении всей серии. У нас есть кнопка, при нажатии на которую число, отображаемое на экране, увеличивается на 1:

А его реализация такова:

class ClickCounter extends React.Component {
    constructor(props) {
        super(props);
        this.state = {count: 0};
        this.handleClick = this.handleClick.bind(this);
    }

    handleClick() {
        this.setState((state) => {
            return {count: state.count + 1};
        });
    }


    render() {
        return [
            <button key="1" onClick={this.handleClick}>Update counter</button>,
            <span key="2">{this.state.count}</span>
        ]
    }
}

ты сможешьздесьПоиграйте с этим. Как видите, этоrenderМетод возвращает два дочерних элемента --buttonа такжеspanпростые компоненты. Как только вы нажмете кнопку, внутри обработчика будет обновлено состояние компонента, а обновление состояния вызоветspanТекст внутри элемента обновляется.

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

  • обновленClickCounterвнутреннее состояние компонентаcountАтрибуты

  • Получить и сравнитьClickCounterПодкомпоненты компонентов и их ProOPS

  • возобновитьspanэлемент реквизита

  • возобновитьspanэлементальtextContentАтрибуты

Помимо вышеуказанных действий, React также выполняет некоторые другие действия во время согласования, такие как вызовметоды жизненного циклаили обновитьrefs. **Все эти действия в архитектуре Fiber называются «заданиями». **Тип работы обычно зависит от типа элемента React. Например, для компонента, определяемого классом, React необходимо создать экземпляр, а для компонента, определяемого функцией, этого не требуется. Как мы знаем, в React есть много типов элементов, таких как: классовые и функциональные компоненты, хост-компоненты (DOM-узлы), порталы и т. д. Тип элемента React задаетсяcreateElementПервое определение параметра функции, которое обычно определяется вrenderспособ создания элемента.

Прежде чем мы начнем изучать детали действий и основное содержание алгоритма Fiber, давайте сначала ознакомимся с некоторыми структурами данных, которые React использует внутри.

От элементов React к узлам Fiber

Каждый компонент в React имеет представление пользовательского интерфейса, мы можем вызвать его изrenderПредставление или шаблон, возвращаемый методом. ЭтоClickCounterШаблон компонента:

<button key="1" onClick={this.onClick}>Update counter</button>
<span key="2">{this.state.count}</span>

Реагировать на элементы

Если шаблон проходит через компилятор JSX, вы получаете набор элементов React. Это возвращается из метода рендеринга компонента React, но не из HTML. Поскольку мы не обязаны использовать JSX, метод рендеринга нашего компонента ClickCounter можно переопределить следующим образом:

class ClickCounter {
    ...
    render() {
        return [
            React.createElement(
                'button',
                {
                    key: '1',
                    onClick: this.onClick
                },
                'Update counter'
            ),
            React.createElement(
                'span',
                {
                    key: '2'
                },
                this.state.count
            )
        ]
    }
}

renderметод называетсяReact.createElementГенерируются следующие две структуры данных:

[
    {
        ?typeof: Symbol(react.element),
        type: 'button',
        key: "1",
        props: {
            children: 'Update counter',
            onClick: () => { ... }
        }
    },
    {
        ?typeof: Symbol(react.element),
        type: 'span',
        key: "2",
        props: {
            children: 0
        }
    }
]

Как видите, React добавляет к этим объектам?typeofатрибуты, чтобы однозначно идентифицировать их как элементы React. Кроме того, у нас есть свойстваtype,keyа такжеpropsдля описания элемента. Эти значения берутся из того, что вы передаете вReact.createElementпараметры функции. Обратите внимание, как React представляет текстовое содержимое какspanа такжеbuttonКак дочерний узел и хуки кликов становятсяbuttonЧасть реквизита элемента. В элементах React есть и другие поля, такие какrefобласть, которая выходит за рамки данной статьи.

а такжеClickCounterЭлемент React не имеет свойств или ключевых атрибутов:

{
    ?typeof: Symbol(react.element),
    key: null,
    props: {},
    ref: null,
    type: ClickCounter
}

Волоконный узел

существует协调период, сrenderДанные для каждого элемента React, возвращаемые методом, объединяются в дерево узлов Fiber. Каждый элемент React имеет соответствующий узел Fiber. В отличие от элементов React, эти волокна не создаются заново при каждом рендеринге. Это изменяемые структуры данных, которые содержат состояние компонента и DOM.

Ранее мы обсуждали, что в зависимости от типа элемента React фреймворк должен выполнять разные действия. В нашем примере приложения для компонентов классаClickCounter, который вызывает методы жизненного цикла иrenderметод и дляspanХост-компонент (узел DOM), выполняющий модификации DOM. Таким образом, каждый элемент React преобразуется всоответствующий типУзел Fiber, описывающий работу, которую необходимо выполнить.

Вы можете думать о волокне как о структуре данных, которая представляет некоторую работу, которую необходимо выполнить, или, скорее, единицу работы. Архитектура Fiber также предоставляет удобный способ отслеживать, планировать, приостанавливать и уничтожать работу.

Когда элемент React впервые преобразуется в узел Fiber, ReactcreateFiberFromTypeAndPropsФункция использует данные элемента для создания волокна. В последующих обновлениях React снова будет использовать узел Fiber и обновлять необходимые свойства данными из соответствующего элемента React. если уже не изrenderМетод возвращает соответствующий элемент React, и React также может потребоватьсяkeyсвойства для перемещения или удаления узлов в иерархии.

ПроверитьChildReconcilerfunctions, чтобы увидеть список всех действий и соответствующих функций, которые React выполняет для существующих узлов Fiber.

Поскольку React создает узел Fiber для каждого элемента React, и поскольку у нас есть дерево этих элементов, мы можем получить дерево узлов Fiber. Для нашего примера приложения это выглядит так:

Все узлы Fiber связаны через связанный список, используяchild,siblingа такжеreturnАтрибуты. Что касается того, почему это работает таким образом, если вы не читали мою статью, для более подробной информации ознакомьтесь сКак и почему React использует связанные списки в Fiber.

Текущее дерево и тренировочное дерево

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

Все работыworkInProgressВыполняется узел Fiber дерева. Когда React проходитcurrentдерева, для каждого существующего узла Fiber React создаетworkInProgressАльтернативный узел для дерева, который будет создан с использованием данных из элемента React, возвращаемых методом рендеринга. Как только обновление будет обработано и вся связанная с этим работа будет выполнена, React будет иметь альтернативное дерево, готовое для обновления на экране. как только этоworkInProgressДерево отображается на экране и становитсяcurrentДерево.

Один из основных принципов React — согласованность. React всегда сразу обновляет DOM — он не показывает частичные промежуточные результаты.workInProgressДерево действует как «черновик», невидимый для пользователя, так что React может обработать все компоненты, прежде чем выводить их изменения на экран.

В исходном коде вы увидите множество функций изcurrentа такжеworkInProgressПолучите узел Fiber из дерева. Вот сигнатура одной из таких функций:

function updateHostComponent(current, workInProgress, renderExpirationTime) {...}

Каждый узел Fiber содержит备用Ссылка на соответствующий раздел домена в другом дереве. отcurrentУзлы в дереве будут указывать наworkInProgressУзел в дереве и наоборот.

побочный эффект

Мы можем думать о компоненте в React как о функции, которая использует состояние и свойства для вычисления представления пользовательского интерфейса. Все остальные действия, такие как изменение DOM или вызов методов жизненного цикла, следует рассматривать как побочные эффекты или просто как эффект.в документеЭто описывается так:

Возможно, вы уже выполняли выборку данных, подписки или ручные изменения DOM в компонентах React. Мы называем эти операции «побочными эффектами» (или просто «эффектами»), потому что они влияют на другие компоненты и не могут выполняться во время рендеринга.

Вы можете видеть, что большинство обновлений состояния и реквизитов вызывают побочные эффекты. Поскольку использование побочных эффектов - это тип работы (активность), волокнистые узлы являются удобным механизмом для отслеживания эффектов, кроме обновлений. Каждый волокно может иметь побочные эффекты, связанные с ним, которые можно найти вeffectTagПолевое кодирование.

Таким образом, побочные эффекты в Fiber в основном определяют, что необходимо сделать для экземпляра после обработки обновления.Работа. Для хост-компонентов (DOM-элементов) так называемая работа включает в себя добавление, обновление или удаление элементов. Для компонентов класса React может потребоваться обновить ссылки и вызватьcomponentDidMountа такжеcomponentDidUpdateМетоды жизненного цикла. Для других типов волокон существуют соответствующие другие побочные эффекты.

список побочных эффектов

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

Цель этого списка — пометить узлы, которые имеют обновления DOM или другие связанные побочные эффекты. Этот списокfinishedWorkподмножество дерева и использоватьnextEffectсвойства вместоcurrentа такжеworkInProgressиспользуется в деревеchildсвойства для ссылки.

Дэн Абрамов приводит аналогию для списков побочных эффектов. Ему нравится думать об этом как о рождественской елке с «рождественскими огнями», связывающими все действительные узлы вместе. Чтобы визуализировать это, давайте представим следующее дерево узлов Fiber, где выделенные узлы должны выполнить некоторую работу. Например, наше обновление привело кc2вставляется в DOM,d2а такжеc1используется для изменения свойств, аb2Используется для запуска методов жизненного цикла. Список побочных эффектов свяжет их вместе, чтобы React мог позже пропустить другие узлы:

Вы можете видеть, как узлы с побочными эффектами связаны друг с другом. При обходе узлов React используетfirstEffectУказатель для определения начальной позиции списка. Таким образом, приведенный выше график можно представить в виде линейного списка, например:

Как видите, React применяет побочные эффекты в порядке от дочернего к родительскому.

Корневой узел дерева волокон

Каждое приложение React имеет один или несколько элементов DOM, которые действуют как контейнеры. В нашем случае это идентификаторcontainerизdivэлемент. React создает по одному контейнеруВолоконный кореньобъект. Вы можете получить к нему доступ, используя ссылку на элемент DOM:

const fiberRoot = query('#container')._reactRootContainer._internalRoot

Корень Fiber — это место, где React хранит ссылку на дерево Fiber, которое хранится в корневом объекте Fiber.currentВ свойствах:

const hostRootFiberNode = fiberRoot.current

Дерево волокон начинается сособый типВолоконный узелHostRootНачинать. Он создается внутри и действует как родитель самого верхнего компонента.HostRootУзлы могут проходитьstateNodeимущество возвращается вFiberRoot:

fiberRoot.current.stateNode === fiberRoot; // true

Вы можете получить доступ к самому верхнему через корень волокнаHostRootnode для изучения дерева Fiber, или вы можете получить отдельные узлы Fiber из экземпляра компонента следующим образом:

compInstance._reactInternalFiber

Структура оптоволоконного узла

Теперь давайте посмотрим наClickCounterСтруктура волоконных узлов, созданных компонентами

{
    stateNode: new ClickCounter,
    type: ClickCounter,
    alternate: null,
    key: null,
    updateQueue: null,
    memoizedState: {count: 0},
    pendingProps: {},
    memoizedProps: {},
    tag: 1,
    effectTag: 0,
    nextEffect: null
}

так же какspanDOM-элемент:

{
    stateNode: new ClickCounter,
    type: ClickCounter,
    alternate: null,
    key: null,
    updateQueue: null,
    memoizedState: {count: 0},
    pendingProps: {},
    memoizedProps: {},
    tag: 1,
    effectTag: 0,
    nextEffect: null
}

На узлах Fiber много полей. Я описал поля в предыдущем разделеalternate,effectTagа такжеnextEffectиспользование. Теперь давайте посмотрим, зачем нужны другие поля.

stateNode

Содержит ссылку на экземпляр класса компонента, узел DOM или другой тип элемента React, связанный с узлом Fiber. В общем, мы можем думать об этом свойстве как о сохранении локального состояния, связанного с узлом Fiber.

type

Функция или класс, определяющий этот узел Fiber. Для компонентов класса он указывает на конструктор, а для элементов DOM — на HTML-разметку. Я часто использую это поле, чтобы понять, с каким элементом связан узел Fiber.

tag

определениеТип волокна. Он используется в алгоритмах координации для определения того, что необходимо сделать. Как упоминалось ранее, задание зависит от типа элемента React. функцияcreateFiberFromTypeAndPropsСопоставьте элементы React с соответствующими типами узлов Fiber. В нашем приложенииClickCounterСвойства компонентаtagравно 1, что указывает на даClassComponent(компонент класса), в то время какspanатрибуты элементовtag5, значит даHostComponent(хост-компонент).

updateQueue

Очередь обновлений состояния, обратных вызовов и обновлений DOM.

memoizedState

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

memoizedProps

Реквизит волокна, используемый для создания вывода в предыдущем рендере.

pendingProps

props, которые были обновлены новыми данными из элемента React и должны быть применены к дочерним компонентам или элементам DOM.

key

Уникальные идентификаторы, которые при наличии набора дочерних элементов помогают React определить, какие элементы были изменены, добавлены или удалены. это сздесьОписывает функциональность «списков и ключей» React.

ты сможешьздесьНайдите полную структуру узла Fiber. Я пропустил кучу полей в объяснении выше. В частности, я пропустил указателиchild,siblingа такжеreturn, они составляют мойпредыдущий постДревовидная структура данных, описанная в . И категории полей, такие как expireTime, childExpirationTime и режим, характерные для планировщика.

Общий алгоритм

React выполняет работу в два основных этапа:renderа такжеcommit.

во-первыхrenderэтап, React проходитsetUpdateилиReact.renderЗапланируйте обновления компонентов и определите, что необходимо обновить в пользовательском интерфейсе. Если это первоначальный рендеринг, ReactrenderКаждый элемент, возвращаемый методом, создает новый узел Fiber. Узлы Fiber для существующих элементов React будут повторно использоваться и обновляться в последующих обновлениях.На этом этапе необходимо пометить дерево узлов Fiber с помощью побочных эффектов.. Побочные эффекты описаны в следующемcommitСтадия должна быть сделана. На этом этапе держатели React отметили дерево побочных эффектов Fiber и прикладные примеры. Он просматривает список побочных эффектов и выполняет другие изменения обновлений DOM, видимые пользователю.

**Что нам нужно понять, так это то, что первыйrenderРабота этапа может выполняться асинхронно. **React может обрабатывать один или несколько узлов Fiber в зависимости от доступных временных отрезков, затем останавливаться для подготовки выполненной работы и разворачиваться для обработки некоторых событий, а затем продолжает работу с того места, где остановился. Но иногда может потребоваться отказаться от проделанной работы и начать сначала. Поскольку работа, выполняемая на этом этапе, не приводит к видимым для пользователя изменениям (например, обновлениям DOM), имеет смысл приостановить поведение. ** Напротив, последующиеcommitФазы всегда синхронизированы. ** Это связано с тем, что работа, выполняемая на этом этапе, приводит к видимым для пользователя изменениям, таким как обновления DOM. Вот почему React должен выполнять эти обновления в одном процессе.

Одна из вещей, которые делает React, — это вызов методов жизненного цикла. Некоторые методы находятся вrenderэтапе, в то время как другие методы вызываются вcommitсценический вызов. это первыйrenderСписок вызовов этапов жизненного цикла:

  • [UNSAFE_]componentWillMount (устарело)

  • [UNSAFE_]componentWillReceiveProps (устарело)

  • getDerivedStateFromProps

  • shouldComponentUpdate

  • [UNSAFE_]componentWillUpdate (устарело)

  • render

Как видите, начиная с версии 16.3, вrenderНекоторые зарезервированные методы жизненного цикла, выполняемые стадией, помечены какUNSAFE, в документации они теперь называются устаревшими жизненными циклами. Они будут объявлены устаревшими в будущих выпусках 16.x безUNSAFEПрефиксные методы будут удалены в версии 17.0. ты сможешьздесьУзнайте больше об этих изменениях и предлагаемом пути миграции.

Итак, какова цель этого?

Ну, мы только что узнали, потому чтоrenderФазы не имеют побочных эффектов, таких как обновления DOM, поэтому React может обрабатывать асинхронные обновления компонентов асинхронно (возможно, даже в нескольких потоках). Однако жизненные циклы с пометкой UNSAFE часто неправильно понимаются и используются неправильно. Разработчики склонны помещать в эти методы код с побочными эффектами, что может вызвать проблемы с новыми методами асинхронного рендеринга. Хотя будут удалены только соответствующие методы без префикса UNSAFE, они по-прежнему могут вызывать проблемы в будущих режимах параллелизма (от которых вы можете отказаться).

Перечисленные далее методы жизненного цикла находятся во второмcommitВыполненные этапы:

  • getSnapshotBeforeUpdate

  • componentDidMount

  • componentDidUpdate

  • componentWillUnmount

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

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

Этап рендеринга

Алгоритм координации всегда используетсяrenderRoot функция сверхуHostRootУзел запускается. Однако React будет пропускать уже обработанные узлы Fiber, пока не найдет узел с незавершенной работой. Например, при вызове компонента глубоко в дереве компонентовsetStateметод, React запустится сверху, но быстро пропустит родителей, пока не дойдет до вызоваsetStateКомпонент метода.

Основные этапы рабочего цикла

Все узлы Fiber будут врабочий циклобработано в. Вот реализация синхронной части цикла:

function workLoop(isYieldy) {
  if (!isYieldy) {
    while (nextUnitOfWork !== null) {
      nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }
  } else {...}
}

В приведенном выше кодеnextUnitOfWorkдержатьworkInProgressСсылка на узел Fiber в дереве, над которым нужно поработать. Когда React проходит по дереву волокон, он использует эту переменную, чтобы узнать, есть ли незавершенная работа на каких-либо других узлах волокон. После обработки текущего волокна переменная будет содержать ссылку на следующий узел волокна в дереве или нуль. В этом случае React выходит из рабочего цикла и готов зафиксировать изменения.

Есть четыре основные функции, используемые для обхода дерева, инициализации или завершения работы:

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

Обратите внимание, что вертикальные линии обозначают отношения на одном уровне, а соединения полилинии указывают на отношения родитель-потомок.Например,b1не имеет дочерних узлов, аb2имеет дочерний узелc1.

в этотвидео, мы можем приостановить воспроизведение и проверить состояние текущего узла и функции. Концептуально вы можете думать о «начале» как о «входе» в компонент, а о «завершении» — как о «выходе» из него. Объясняя, что делают эти функции, вы также можетездесьИспользуйте примеры и реализации.

Сначала мы начали изучатьperformUnitOfWorkа такжеbeginWorkЭти две функции:

function performUnitOfWork(workInProgress) {
    let next = beginWork(workInProgress);
    if (next === null) {
        next = completeUnitOfWork(workInProgress);
    }
    return next;
}

function beginWork(workInProgress) {
    console.log('work performed for ' + workInProgress.name);
    return workInProgress.child;
}

функцияperformUnitOfWorkотworkInProgressДерево получает узел Fiber и вызывает его, вызываяbeginWorkФункция начинает работать. Эта функция запускает все действия, необходимые для работы Fiber. В демонстрационных целях мы выходим из системы только по имени оптоволоконного узла, чтобы показать, что работа выполнена.функцияbeginWorkВсегда возвращает указатель на следующий дочерний узел для обработки в цикле или null.

Если есть следующий дочерний узел, он будет назначенworkLoopпеременная в функцииnextUnitOfWork. Однако, если дочерних узлов нет, React знает, что достиг конца ветки, поэтому может завершить текущий узел. **После того, как узел будет готов, ему нужно будет выполнить работу для других узлов в том же слое и вернуться к родительскому элементу, когда это будет сделано. **ЭтоcompleteUnitOfWorkКод, выполняемый функцией:

function completeUnitOfWork(workInProgress) {
    while (true) {
        let returnFiber = workInProgress.return;
        let siblingFiber = workInProgress.sibling;

        nextUnitOfWork = completeWork(workInProgress);

        if (siblingFiber !== null) {
            // If there is a sibling, return it
            // to perform work for this sibling
            return siblingFiber;
        } else if (returnFiber !== null) {
            // If there's no more work in this returnFiber,
            // continue the loop to complete the parent.
            workInProgress = returnFiber;
            continue;
        } else {
            // We've reached the root.
            return null;
        }
    }
}

function completeWork(workInProgress) {
    console.log('work completed for ' + workInProgress.name);
    return null;
}

Вы можете видеть, что ядро ​​функции — это просто большойwhileЦикл. когдаworkInProgressReact входит в эту функцию, когда у узла нет дочерних элементов. После завершения работы текущего узла волокна он проверяет наличие одноранговых узлов. Если он найден, React выходит из функции и возвращает указатель на одноранговый узел. он будет назначенnextUnitOfWorkпеременная, React начнет ветвление с этого узла. Нам нужно понять, что на текущем узле React только завершает работу предыдущих узлов того же уровня. Он еще не выполнил работу родительского узла.Работа родительского узла и откат могут быть выполнены только после завершения всех ветвей, начинающихся с дочернего узла.

Как видно из реализации,performUnitOfWorkа такжеcompleteUnitOfWorkв основном используется для итерационных целей, в то время как основная деятельностьbeginWorkа такжеcompleteWorkв функции. В следующей серии статей мы увидим, как React входитbeginWorkа такжеcompleteWorkфункция,ClickCounterкомпоненты иspanчто происходит с узлом.

commitсцена

Этот этап начинается с функцииcompleteRoot Начинать. На этом этапе React обновляет DOM и вызывает методы до и после жизненного цикла изменения.

Когда React входит в эту стадию, у него есть 2 дерева и список побочных эффектов. Первое дерево представляет состояние, отображаемое в данный момент на экране, а затем вrenderstage строит альтернативное дерево. он вызывается в исходном кодеfinishedWorkилиworkInProgress, указывающее состояние, которое необходимо отобразить на экране. Это альтернативное дерево будет передано аналогичным образом.childа такжеsiblingУказатель на ссылку наcurrentДерево.

Затем, есть список побочных эффектов - этоfinishedWorkПодмножество узлов дерева, черезnextEffectуказатель на ссылку. Следует помнить, что список побочных эффектов работаетrenderсценарезультат. Весь смысл рендеринга заключается в том, чтобы определить, какие узлы нужно вставить, обновить или удалить, и какие компоненты должны вызывать свои методы жизненного цикла. Вот что говорит нам список побочных эффектов,эта страница находится вcommitКоллекция узлов для этапа, по которому будет выполняться итерация.

В целях отладки можно изменить свойства корня Fiber.currentдоступcurrentДерево. в состоянии пройтиcurrentв деревеHostFiberузлаalternateдоступ к собственностиfinishedWorkДерево.

существуетcommitОсновная функция, которую выполняет сцена, этоcommitRoot . Он делает следующее:

  • отмечен какSnapshotПобочные эффекты вызываются на узлеgetSnapshotBeforeUpdateЖизненный цикл

  • отмечен какDeletionПобочные эффекты вызываются на узлеcomponentWillUnmountЖизненный цикл

  • Выполнить все вставки, обновления, удаления DOM

  • БудуfinishedWorkдерево настроено на текущее

  • отмечен какPlacementПобочные эффекты вызываются на узлеcomponentDidMountЖизненный цикл

  • отмечен какUpdateПобочные эффекты вызываются на узлеcomponentDidUpdateЖизненный цикл

перед вызовом метода измененияgetSnapshotBeforeUpdateПосле этого React фиксирует все побочные эффекты в дереве, что делается в две волны. Первая волна выполняет все вставки, обновления, удаления и выгрузки ссылок DOM (хоста). Тогда React будетfinishedWorkприсвоение дереваFiberRoot,БудуworkInProgressдерево помечено какcurrentДерево. Это делается после первой волны фазы фиксации и перед второй волной, поэтому вcomponentWillUnmountв предыдущем дереве все еще актуален, вcomponentDidMount/UpdateРаботы, выполненные за период, являются текущими. Во второй волне React вызывает все остальные методы жизненного цикла и обратные вызовы ссылок. Эти методы передаются для выполнения по отдельности, гарантируя, что все вставки, обновления и удаления во всем дереве инициируются для выполнения.

Вот суть функции, которая выполняет описанные выше шаги:

function commitRoot(root, finishedWork) {
    commitBeforeMutationLifecycles()
    commitAllHostEffects();
    root.current = finishedWork;
    commitAllLifeCycles();
}

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

Методы жизненного цикла до обновления

Например, это обход дерева побочных эффектов и проверка наличия узла.SnapshotКод побочного эффекта:

function commitBeforeMutationLifecycles() {
    while (nextEffect !== null) {
        const effectTag = nextEffect.effectTag;
        if (effectTag & Snapshot) {
            const current = nextEffect.alternate;
            commitBeforeMutationLifeCycles(current, nextEffect);
        }
        nextEffect = nextEffect.nextEffect;
    }
}

Для компонента класса этот побочный эффект означает, что вызовgetSnapshotBeforeUpdateМетоды жизненного цикла.

обновление модели каталога

commitAllHostEffectsэто функция React для выполнения обновлений DOM. Эта функция в основном определяет тип операций, которые должен выполнять узел, и выполняет следующие операции:

function commitAllHostEffects() {
    switch (primaryEffectTag) {
        case Placement: {
            commitPlacement(nextEffect);
            ...
        }
        case PlacementAndUpdate: {
            commitPlacement(nextEffect);
            commitWork(current, nextEffect);
            ...
        }
        case Update: {
            commitWork(current, nextEffect);
            ...
        }
        case Deletion: {
            commitDeletion(nextEffect);
            ...
        }
    }
}

Интересно, что React вызываетcomponentWillUnmountметод какcommitDeletionЧасть процедуры удаления в функции.

Обновленные методы жизненного цикла

commitAllLifecyclesэто функция, которую React вызывает все оставшиеся методы жизненного цикла. В текущей реализации React будет вызываться единственный метод измененияcomponentDidUpdate.

Мы наконец закончили. Дайте мне знать, что вы думаете об этой статье, или задайте вопросы в комментариях. У меня также есть статьи о планировщиках, процессах субсогласования и о том, как создавать списки побочных эффектов, чтобы предоставить их подробное объяснение. Также я планирую снять видео, где покажу, как отлаживать приложение, взяв за основу эту статью.

Статья может быть воспроизведена по желанию, но просьба сохранитьОригинальная ссылка. Добро пожаловать!ES2049 Studio, отправьте свое резюме на caijun.hcj(at)alibaba-inc.com.