В архитектуру React Fiber

React.js
В архитектуру React Fiber

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

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

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

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

Но пока мы наслаждаемся комфортным опытом разработки, у нас в голове остаются некоторые вопросы:

  • Что приводит к частому зависанию реакции взаимодействия с пользователем и анимации
  • Как изящно обрабатывать исключения, перехватывать исключения и отображать альтернативные пользовательские интерфейсы
  • Как лучше реализовать повторное использование и управление состоянием компонентов

Это искажение человеческой натуры или потеря морали / собачья голова

Может ли Файбер дать нам ответ, какие сюрпризы он нам принесет, и поднимет новую волну, добро пожаловать на просмотр "В Файбер"

Итак, вкратце, что такое React Fiber?

FiberЭто реконструкция основного алгоритма React, а продукт двухлетней реконструкции — согласователь волокон.

что такое координация реакции

Согласование — важная часть реакции, которая включает в себя то, как сравнивать старые и новые различия деревьев, чтобы перейти к той части, где обновляются только различия.

Согласование и рендеринг теперь разделены на два разных этапа после рефакторинга реакции.

  • этап координации выверки: Когда компонент инициализируется и его состояние обновляется, React создаст два разных виртуальных дерева. React должен определить, как эффективно обновлять пользовательский интерфейс на основе разницы между двумя деревьями, чтобы гарантировать, что текущий пользовательский интерфейс и последний синхронизируются, вычисляя, какие части дерева необходимо обновить.
  • стадия визуализации: модуль рендеринга отвечает за обновление и рендеринг полученной информации о дереве виртуальных компонентов в приложении в соответствии с его соответствующей средой. Заинтересованные друзья могут посмотреть статьи в собственном блоге Дэна = "React => Рендерер во время выполнения, который представляет средства визуализации React Renderer, такие как react-dom и react native, которые могут генерировать разные экземпляры в соответствии с различными основными средами.

Зачем переписывать координацию

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

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

Кадр: в процессе анимации каждое неподвижное изображение представляет собой «кадр»;
Частота кадров: мера, используемая для измерения количества отображаемых кадров, а единицей измерения является «Кадр в секунду» (Frame per Second, FPS) или «Герц»;
Длительность кадра: время задержки каждого неподвижного изображения, обычно в мс (миллисекундах);
Потеря кадров: в анимации с фиксированной частотой кадров продолжительность определенного кадра намного превышает среднюю продолжительность кадра, что приводит к тому, что последующие кадры сжимаются и теряются;

В настоящее время общая частота кадров большинства ноутбуков и мобильных телефонов составляет 60 Гц, то есть за одну секунду отображается 60 кадров изображений, а время задержки одного кадра составляет 16,7 мс (1000/60≈16,7), что оставляет разработчикам и системы пользовательского интерфейса около 16,67 мс, чтобы выполнить всю работу, необходимую для создания неподвижного изображения (кадра). Если эти задачи не будут выполнены в течение выделенных 16,67 мс, это вызовет последствия «пропущенных кадров», что сделает работу интерфейса менее плавной.

Поток рендеринга GUI и поток JS-движка в браузере

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

Браузер переполнен основной веткой

До того, как React16 запустил Fiber, алгоритмом координации был Stack Reconciler, который рекурсивно обходит все узлы Virtual DOM для выполнения алгоритма Diff. После запуска его нельзя прервать. Основной поток не будет освобожден, пока не будет построено все дерево виртуального DOM. из-за однопоточной функции JavaScript. , если текущий компонент имеет сложную вложенность и логическую обработку, diff заблокирует процесс пользовательского интерфейса, так что задачи с относительно высоким приоритетом, такие как анимация и взаимодействие, не могут быть обработаны немедленно, вызывая страницу замораживать и пропускать кадры, влияя на работу пользователя.

Себ официально упомянул концепцию Fiber в Facebook в 2016 году, объяснив, почему фреймворк был переписан:

Once you have each stack frame as an object on the heap you can do clever things like reusing it during future updates and yielding to the event loop without losing any of your currently in progress data.
После каждого кадра стека в качестве объекта на куче вы можете сделать некоторые умные вещи, такие как повторное использование его в петле события и подвешены в будущем обновлении, не теряя никаких данных, которые в настоящее время выполняются.

Давайте проведем эксперимент

function randomHexColor() {
  return (
    "#" + ("0000" + ((Math.random() * 0x1000000) << 0).toString(16)).substr(-6)
  );
}

var root = document.getElementById("root");

// 一次性遍历100000次
function a() {
  setTimeout(function() {
    var k = 0;
    for (var i = 0; i < 10000; i++) {
      k += new Date() - 0;
      var el = document.createElement("div");
      el.innerHTML = k;
      root.appendChild(el);
      el.style.cssText = `background:${randomHexColor()};height:40px`;
    }
  }, 1000);
}

// 每次只操作100个节点,共100次
function b() {
  setTimeout(function() {
    function loop(n) {
      var k = 0;
      console.log(n);
      for (var i = 0; i < 100; i++) {
        k += new Date() - 0;
        var el = document.createElement("div");
        el.innerHTML = k;
        root.appendChild(el);
        el.style.cssText = `background:${randomHexColor()};height:40px`;
      }
      if (n) {
        setTimeout(function() {
          loop(n - 1);
        }, 40);
      }
    }
    loop(100);
  }, 1000);
}

скриншот производительности исполнения: серьезные просадки кадров, общий fps 1139,6 мс

b Скриншот производительности выполнения: fps находится между 15 мс и 19 мс

Причина в том, что основной поток браузера должен обрабатывать рендеринг графического интерфейса, обработку таймера, обработку событий, выполнение JS, удаленную загрузку ресурсов и т. д. Когда одна вещь сделана, следующая может быть сделана только после того, как она будет сделана. Если времени достаточно, браузер скомпилирует и оптимизирует наш код (JIT) и выполнит горячую оптимизацию кода, а некоторые операции DOM также обработают перекомпоновку внутри. Перекомпоновка — это черная дыра производительности, которая может привести к изменению макета большинства элементов страницы.

Как передовое блюдо с мечтой🐤, наша прямая обязанность - предоставить лучший интерактивный опыт отцу пользователя. Мы несем трудности на своих плечах. Давайте посмотрим, как реакция решает вышеуказанные проблемы.

Файбер ты что (четвертый тон)

Итак, давайте сначала посмотрим, что такое Fiber как решение, а затем проанализируем, почему он может решить вышеуказанные проблемы.

определение:

  1. Повторная реализация базового алгоритма согласования Reconciliation.
  2. кадр виртуального стека
  3. Объект js со структурой хранения данных в виде сглаженного связанного списка — наименьшая единица работы, которую можно разделить на этапе согласования.

Расширим его определение:

Фрейм виртуального стека:

Эндрю КларкДокументация по системе React FibreИдея реализации Fiber хорошо объяснена, я процитирую ее здесь:

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

Модель выполнения JavaScript: стек вызовов

Собственная модель выполнения JavaScript: состояние выполнения функции управляется через стек вызовов.
Каждый кадр стека представляет собой единицу работы, в которой хранится такая информация, как указатель возврата вызова функции, текущая функция, параметры вызова и локальные переменные. Поскольку стек выполнения JavaScript управляется движком, после запуска стека выполнения он будет продолжать выполняться до тех пор, пока стек выполнения не будет опустошен. Нельзя прервать по требованию.

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

управляемый стек вызовов

Так что в идеале процесс согласования должен быть таким, как показано на рисунке ниже, с разделением тяжелых задач на небольшие рабочие блоки, и вы сможете «вздохнуть» после того, как закончите. Нам нужно планирование инкрементного рендеринга.Fiber должен заново реализовать планирование кадров стека, которое может выполнять их в соответствии со своим собственным алгоритмом планирования. Кроме того, поскольку эти стеки могут разбивать прерываемые задачи на несколько подзадач, подзадачи можно свободно планировать в соответствии с их приоритетами и обновлять по сегментам, тем самым заменяя предыдущую синхронную визуализацию на асинхронную.

Его особенности - разделение времени и приостановка.

Объект js со структурой хранения данных в виде сглаженного связанного списка:

Fiber — это объект js. Создание волокна создается элементами React. В виртуальном дереве DOM, построенном React, каждый элемент соответствует волокну, таким образом, создавая дерево волокон. Каждое волокно не только содержит Информация о каждом элементе также содержит больше информации, чтобы облегчить планировщик для планирования.

Давайте посмотрим на структуру волокна.

type Fiber = {|
  // 标记不同的组件类型
  //export const FunctionComponent = 0;
  //export const ClassComponent = 1;
  //export const HostRoot = 3; 可以理解为这个fiber是fiber树的根节点,根节点可以嵌套在子树中
  //export const Fragment = 7;
  //export const SuspenseComponent = 13;
  //export const MemoComponent = 14;
  //export const LazyComponent = 16;
  tag: WorkTag,

  // ReactElement里面的key
  // 唯一标示。我们在写React的时候如果出现列表式的时候,需要制定key,这key就是对应元素的key。
  key: null | string,

  // ReactElement.type,也就是我们调用`createElement`的第一个参数
  elementType: any,

  // The resolved function/class/ associated with this fiber.
  // 异步组件resolved之后返回的内容,一般是`function`或者`class`
  type: any,

  // The local state associated with this fiber.
  // 跟当前Fiber相关本地状态(比如浏览器环境就是DOM节点)
  // 当前组件实例的引用
  stateNode: any,

  // 指向他在Fiber节点树中的`parent`,用来在处理完这个节点之后向上返回
  return: Fiber | null,

  // 单链表树结构
  // 指向自己的第一个子节点
  child: Fiber | null,
  // 指向自己的兄弟结构
  // 兄弟节点的return指向同一个父节点
  sibling: Fiber | null,
  index: number,

  // ref属性
  ref: null | (((handle: mixed) => void) & {_stringRef: ?string}) | RefObject,

  // 新的变动带来的新的props
  pendingProps: any, 
  // 上一次渲染完成之后的props
  memoizedProps: any,

  // 该Fiber对应的组件产生的Update会存放在这个队列里面
  updateQueue: UpdateQueue<any> | null,

  // 上一次渲染的时候的state
  // 用来存放某个组件内所有的 Hook 状态
  memoizedState: any,

  // 一个列表,存放这个Fiber依赖的context
  firstContextDependency: ContextDependency<mixed> | null,

  // 用来描述当前Fiber和他子树的`Bitfield`
  // 共存的模式表示这个子树是否默认是异步渲染的
  // Fiber被创建的时候他会继承父Fiber
  // 其他的标识也可以在创建的时候被设置
  // 但是在创建之后不应该再被修改,特别是他的子Fiber创建之前
  //用来描述fiber是处于何种模式。用二进制位来表示(bitfield),后面通过与来看两者是否相同//这个字段其实是一个数字.实现定义了一下四种//NoContext: 0b000->0//AsyncMode: 0b001->1//StrictMode: 0b010->2//ProfileMode: 0b100->4
  mode: TypeOfMode,

  // Effect
  // 用来记录Side Effect具体的执行的工作的类型:比如Placement,Update等等
  effectTag: SideEffectTag,

  // 单链表用来快速查找下一个side effect
  nextEffect: Fiber | null,

  // 子树中第一个side effect
  firstEffect: Fiber | null,
  // 子树中最后一个side effect
  lastEffect: Fiber | null,

  // 代表任务在未来的哪个时间点应该被完成
  // 不包括他的子树产生的任务
  // 通过这个参数也可以知道是否还有等待暂停的变更、没有完成变更。
  // 这个参数一般是UpdateQueue中最长过期时间的Update相同,如果有Update的话。
  expirationTime: ExpirationTime,

  // 快速确定子树中是否有不在等待的变化
  childExpirationTime: ExpirationTime,

  //当前fiber对应的工作中的Fiber。
  // 在Fiber树更新的过程中,每个Fiber都会有一个跟其对应的Fiber
  // 我们称他为 current <==> workInProgress
  // 在渲染完成之后他们会交换位置
  alternate: Fiber | null,
  ...
|};

Тип компонента ReactWorkTags

структура связанного списка

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

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

текущий и workInProgress

текущее дерево: когда React выполняет рендеринг в первый раз, он создаст дерево элементов с помощью React.createElement, которое можно назвать виртуальным деревом DOM.Из-за необходимости записи контекстной информации добавляется Fiber, каждый элемент будет соответствовать узлу Fiber, и Fiber Связанная структура узлов становится деревом волокон. Он отражает состояние, используемое для визуализации пользовательского интерфейса и сопоставления приложения. Это дерево часто называют текущим деревом (текущим деревом, которое записывает состояние текущей страницы).

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

function createWorkInProgress(current, ...) {
  let workInProgress = current.alternate;
  if (workInProgress === null) {
    workInProgress = createFiber(...);
  }
  ...
  workInProgress.alternate = current;
  current.alternate = workInProgress;
  ...
  return workInProgress;
}

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

Дэн вBeyond React 16В выступлении была использована очень удачная аналогия — ветки функций Git. Дерево WIP можно рассматривать как ветку функций, ответвляющуюся от старого дерева. Вы добавляете или удаляете функции из этой новой ветви, даже если это ошибка. Не повлияет на старые ветки. Когда ваша ветка протестирована и доработана, вы можете слиться со старой веткой и заменить ее.

Update

  • Используется для записи изменений в состоянии компонента
  • Хранится в очереди обновлений волокна
  • Несколько обновлений существуют одновременно

Например, если вы установите три setState(), React не будет обновлять их сразу, а поместит в UpdateQueue, а затем обновит.

ps: setState всегда задавался вопросом, почему он не синхронный, а setState() рассматривается как запрос, а не как команда для немедленного обновления компонента. Для лучшей воспринимаемой производительности React вызывает его лениво, а затем обновляет несколько компонентов за один проход. React не гарантирует, что изменения состояния вступят в силу немедленно.

export function createUpdate(
  expirationTime: ExpirationTime,
  suspenseConfig: null | SuspenseConfig,
): Update<*> {
  let update: Update<*> = {
    //任务过期事件
    //在创建每个更新的时候,需要设定过期时间,过期时间也就是优先级。过期时间越长,就表示优先级越低。
    expirationTime,
    // suspense的配置
    suspenseConfig,

  // export const UpdateState = 0; 表示更新State
  // export const ReplaceState = 1; 表示替换State
  // export const ForceUpdate = 2; 强制更新
  // export const CaptureUpdate = 3; 捕获更新(发生异常错误的时候发生)
  // 指定更新的类型,值为以上几种
    tag: UpdateState,
    // 更新内容,比如`setState`接收的第一个参数
    payload: null,
    // 更新完成后的回调,`setState`,`render`都有
    callback: null,

    // 指向下一个update
    // 单链表update queue通过 next串联
    next: null,
    
    // 下一个side effect
    // 最新源码被抛弃 next替换
    //nextEffect: null,
  };
  if (__DEV__) {
    update.priority = getCurrentPriorityLevel();
  }
  return update;
}

UpdateQueue

//创建更新队列
export function createUpdateQueue<State>(baseState: State): UpdateQueue<State> {
  const queue: UpdateQueue<State> = {
    //应用更新后的state
    baseState,
    //队列中的第一个update
    firstUpdate: null,
    //队列中的最后一个update
    lastUpdate: null,
     //队列中第一个捕获类型的update
    firstCapturedUpdate: null,
    //队列中最后一个捕获类型的update
    lastCapturedUpdate: null,
    //第一个side effect
    firstEffect: null,
    //最后一个side effect
    lastEffect: null,
    firstCapturedEffect: null,
    lastCapturedEffect: null,
  };
  return queue;
}

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

setState({}, callback); // stack conciler
setState(() => { return {} }, callback); // fiber conciler

Исходный код ReactUpdateQueue

Updater

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

const classComponentUpdater = {
  isMounted,
  enqueueSetState(inst, payload, callback) {
    const fiber = getInstance(inst);
    const currentTime = requestCurrentTimeForUpdate();
    const suspenseConfig = requestCurrentSuspenseConfig();
    const expirationTime = computeExpirationForFiber(
      currentTime,
      fiber,
      suspenseConfig,
    );

    const update = createUpdate(expirationTime, suspenseConfig);
    update.payload = payload;
    if (callback !== undefined && callback !== null) {
      if (__DEV__) {
        warnOnInvalidCallback(callback, 'setState');
      }
      update.callback = callback;
    }

    enqueueUpdate(fiber, update);
    scheduleWork(fiber, expirationTime);
  },
  enqueueReplaceState(inst, payload, callback) {
    //一样的代码
    //...
    update.tag = ReplaceState;
    //...
  },
  enqueueForceUpdate(inst, callback) {
    //一样的代码
    //...
    update.tag = ForceUpdate;
    //...
  },
};

ReactUpdateQueue=>classComponentUpdater

Effect list

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

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

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

С каждым узлом волокна могут быть связаны эффекты, представленные полем effectTag в узле волокна.

Цель этого списка заключается в том, чтобы пометить узлы с обновлениями DOM или другими эффектами, связанными с ними, что является подмножеством WIP дерева и использует свойство NextFFect вместо дочерней собственности, используемой в деревьях нынешних и рабочей силы.

How it work

Основная цель

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

Обзор процесса обновления

Давайте взглянем на процесс обновления его волокна, а затем начнем с основной технологии в этом процессе.

Согласование делится на два этапа: согласование и фиксация.

reconciliation

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

  1. Первая часть начинается с метода ReactDOM.render(), который преобразует полученный элемент React в узел Fiber, устанавливает его приоритет, записывает обновления и т. д. Эта часть в основном посвящена подготовке данных.
  2. Вторая часть состоит в основном из трех функций: scheduleWork, requestWork, PerformWork, то есть трилогия организации работы, подачи заявки на работу и формальной работы. В этой части реализована новая функция асинхронного вызова React 16.
  3. Третья часть представляет собой большой цикл, который проходит по всем узлам Fiber, вычисляет всю работу по обновлению с помощью алгоритма Diff и создает список эффектов для использования на этапе фиксации. Сердцем этой части является функция beginWork.

этап фиксации

Основная работа на этом этапе состоит в том, чтобы получить всю работу по обновлению, произведенную на этапе согласования, отправить эту работу и вызвать модуль рендеринга (react-dom) для рендеринга пользовательского интерфейса. После завершения рендеринга пользовательского интерфейса будут вызваны оставшиеся функции жизненного цикла, поэтому в этой части также будет выполняться обработка исключений.

назначить приоритет

В структуре волокон, перечисленных выше, есть время истечения срока действия.

expireTime, по сути, является приоритетом выполнения работы с волокном.

// 源码中的priorityLevel优先级划分
export const NoWork = 0;
// 仅仅比Never高一点 为了保证连续必须完整完成
export const Never = 1;
export const Idle = 2;
export const Sync = MAX_SIGNED_31_BIT_INT;//整型最大数值,是V8中针对32位系统所设置的最大值
export const Batched = Sync - 1;

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



//为fiber对象计算expirationTime
function computeExpirationForFiber(currentTime: ExpirationTime, fiber: Fiber) {
  ...
  // 根据调度优先级计算ExpirationTime
    const priorityLevel = getCurrentPriorityLevel();
    switch (priorityLevel) {
      case ImmediatePriority:
        expirationTime = Sync;
        break;
        //高优先级 如由用户输入设计交互的任务
      case UserBlockingPriority:
        expirationTime = computeInteractiveExpiration(currentTime);
        break;
        // 正常的异步任务
      case NormalPriority:
        // This is a normal, concurrent update
        expirationTime = computeAsyncExpiration(currentTime);
        break;
      case LowPriority:
      case IdlePriority:
        expirationTime = Never;
        break;
      default:
        invariant(
          false,
          'Unknown priority level. This error is likely caused by a bug in ' +
            'React. Please file an issue.',
        );
    }
    ...
}

export const LOW_PRIORITY_EXPIRATION = 5000
export const LOW_PRIORITY_BATCH_SIZE = 250

export function computeAsyncExpiration(
  currentTime: ExpirationTime,
): ExpirationTime {
  return computeExpirationBucket(
    currentTime,
    LOW_PRIORITY_EXPIRATION,
    LOW_PRIORITY_BATCH_SIZE,
  )
}

export const HIGH_PRIORITY_EXPIRATION = __DEV__ ? 500 : 150
export const HIGH_PRIORITY_BATCH_SIZE = 100

export function computeInteractiveExpiration(currentTime: ExpirationTime) {
  return computeExpirationBucket(
    currentTime,
    HIGH_PRIORITY_EXPIRATION,
    HIGH_PRIORITY_BATCH_SIZE,
  )
}

function computeExpirationBucket(
  currentTime,
  expirationInMs,
  bucketSizeMs,
): ExpirationTime {
  return (
    MAGIC_NUMBER_OFFSET -
    ceiling(
    // 之前的算法
     //currentTime - MAGIC_NUMBER_OFFSET + expirationInMs / UNIT_SIZE,
      MAGIC_NUMBER_OFFSET - currentTime + expirationInMs / UNIT_SIZE,
      bucketSizeMs / UNIT_SIZE,
    )
  );
}
// 我们把公式整理一下:
// low
 1073741821-ceiling(1073741821-currentTime+500,25) =>
 1073741796-((1073742321-currentTime)/25 | 0)*25
// high 
1073741821-ceiling(1073741821-currentTime+15,10)

Проще говоря, окончательный результат увеличивается на 25. Например, если мы вводим от 102 до 126, окончательный результат равен 625, но когда он достигает 127, результат равен 650, который делится на 25 и округляется в большую сторону. Эффект.

То есть рассчитанный интервал expireTime низкоприоритетного обновления React составляет 25 мс. React позволяет двум аналогичным (в пределах 25 мс) обновлениям получить одно и то же значение expireTime. Цель состоит в том, чтобы автоматически объединить эти два обновления в одно обновление, чтобы достичь цель пакетных обновлений. Как и в случае с doubleBuffer, в React очень много внимания уделяется повышению производительности!

исходный код алгоритма истечения срока действия

Рекомендуемое чтение:Jokcy God Analysis = "Расчет времени истечения срока действия

приоритет выполнения

Так как же Fiber обеспечивает согласованное асинхронное выполнение задач с разными приоритетами?

Вот введение в два API, предоставляемых браузером: requestIdleCallback и requestAnimationFrame:

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

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

requestAnimationFrame: сообщите браузеру, что вы хотите выполнить анимацию, и попросите браузер вызвать указанную функцию обратного вызова, чтобы обновить анимацию перед следующей перерисовкой.

Совместное планирование: это «контрактное» планирование, которое требует, чтобы наша программа и браузер были тесно интегрированы и доверяли друг другу. Например, браузер может назначить нам квант времени выполнения, за это время мы должны завершить выполнение согласно договоренности и вернуть управление браузеру.

Что делает Fiber, так это то, что ему нужно разложить задачу рендеринга, а затем использовать планирование API в соответствии с приоритетом для асинхронного выполнения указанной задачи:

  • Задачи с низким приоритетом обрабатываются функцией requestIdleCallback, которая ограничивает время выполнения задачи для разделения задач, избегая при этом длительного выполнения задач, блокируя отрисовку пользовательского интерфейса и вызывая пропуски кадров.
  • Задачи с высоким приоритетом, такие как задачи, связанные с анимацией, обрабатываются requestAnimationFrame;

Не все браузеры поддерживают requestIdleCallback, но React внутри реализует собственный полифилл, так что не беспокойтесь о проблемах совместимости браузеров. Реализация полифилла в основном реализована через rAF+postmessage (в последней версии убран rAF, интересующиеся детской обувью могут глянуть ="SchedulerHostConfig

Жизненный цикл

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

Устаревший:

  • componentWillMount
  • componentWillUpdate
  • componentWillReceiveProps

Добавлен:

newLifeCircle

Почему в новом жизненном цикле используются статические

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

Через метод написания статических и функциональных параметров вы можете почувствовать, что React говорит мне: пожалуйста, устанавливайте производное состояние только в соответствии с newProps и не вызывайте через него вспомогательные методы, которые могут становиться все более и более запутанными. С технической точки зрения: getDerivedStateFromProps должна быть чистой функцией без побочных эффектов.

В чем разница между getDerivedStateFromError и componentDidCatch?

Короче говоря, потому чторазные этапыИ функция другая.

getDerivedStateFromError запускается на этапе согласования, поэтому getDerivedStateFromError фиксирует ошибку, а затем изменяет состояние компонента, а побочные эффекты не допускаются.

static getDerivedStateFromError(error) {
    // 更新 state 使下一次渲染可以显降级 UI
    return { hasError: true };
}

componentDidCatch позволяет выполнять побочные эффекты, поскольку он находится в фазе фиксации. Его следует использовать для регистрации таких вещей, как ошибки:

componentDidCatch(error, info) {
    // "组件堆栈" 例子:
    //   in ComponentThatThrows (created by App)
    //   in ErrorBoundary (created by App)
    //   in div (created by App)
    //   in App
    logComponentStackToMyService(info.componentStack);
  }

Информация, связанная с жизненным циклом, нажмите здесь = "Жизненный цикл

Suspense

Реализация Suspense странная и противоречивая.
По словам самого Дэна: ты возненавидишь это, а потом полюбишь его.

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

Приостановка временно используется только для разделения кода с ленивым, возможностью «приостановить» рендеринг, пока компонент чего-то ждет, и отображать загрузку загрузки, но ее роль намного больше. режим экспериментальной стадии документа.Метод приостановки обработки асинхронных запросов на получение данных.

Применение

// 懒加载组件切换时显示过渡组件
const ProfilePage = React.lazy(() => import('./ProfilePage')); // Lazy-loaded

// Show a spinner while the profile is loading
<Suspense fallback={<Spinner />}>
  <ProfilePage />
</Suspense>
// 异步获取数据
import { unstable_createResource } from 'react-cache'

const resource = unstable_createResource((id) => {
  return fetch(`/demo/${id}`)
})

function ProfilePage() {
  return (
    <Suspense fallback={<h1>Loading profile...</h1>}>
      <ProfileDetails />
      <Suspense fallback={<h1>Loading posts...</h1>}>
        <ProfileTimeline />
      </Suspense>
    </Suspense>
  );
}

function ProfileDetails() {
  // Try to read user info, although it might not have loaded yet
  const user = resource.user.read();
  return <h1>{user.name}</h1>;
}

function ProfileTimeline() {
  // Try to read posts, although they might not have loaded yet
  const posts = resource.posts.read();
  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>{post.text}</li>
      ))}
    </ul>
  );
}
  • В функции рендеринга мы можем написать асинхронный запрос на запрос данных
  • react будет читать этот кеш из нашего кеша
  • Если есть кеш, переходим сразу к обычному рендеру
  • Если кеша нет, то будет выброшено исключение, это исключение является промисом
  • Когда обещание выполнено (данные запроса завершены), реакция будет продолжать возвращаться к исходному рендерингу (фактически повторно выполнять рендеринг) и отображать данные.
  • Полностью синхронная запись, никаких асинхронных обратных вызовов.

Если вы не понимаете, что это значит, я могу просто сформулировать это следующим образом:

Вызвать функцию рендеринга -> найти асинхронный запрос -> навести курсор, дождаться результата асинхронного запроса -> снова отрендерить отображаемые данные

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

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

принцип

Взгляните на исходный код нестабильного_createResource, предоставленный react

export function unstable_createResource(fetch, maybeHashInput) {
  const resource = {
    read(input) {
      ...
      const result = accessResult(resource, fetch, input, key);
      switch (result.status) {
        // 还未完成直接抛出自身promise
        case Pending: {
          const suspender = result.value;
          throw suspender;
        }
        case Resolved: {
          const value = result.value;
          return value;
        }
        case Rejected: {
          const error = result.value;
          throw error;
        }
        default:
          // Should be unreachable
          return (undefined: any);
      }
    },
  };
  return resource;
}

Для этого React использует промисы. Компонент может генерировать Promise в своем методе рендеринга (или что-либо, вызываемое в процессе рендеринга компонента, например, новый статический getDerivedStateFromProps). React перехватывает брошенный промис и ищет в дереве ближайший компонент Suspense.Сама Suspense имеет componentDidCatch, который фиксирует промис как ошибку и ждет, пока его выполнение завершит свое состояние изменения, чтобы повторно отобразить дочерние компоненты.

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

Как добиться исключения

  1. Функция renderRoot на этапе согласования, соответствующий метод обработки исключений — throwException
  2. Функция commitRoot на этапе фиксации, соответствующий метод обработки исключений — диспетчеризация.

Перехват исключения на этапе согласования

в реагирующем примирителеperformConcurrentWorkOnRoot

// This is the entry point for every concurrent task, i.e. anything that
// goes through Scheduler.
// 这里是每一个通过Scheduler的concurrent任务的入口
function performConcurrentWorkOnRoot(root, didTimeout) {
    ...
    do {
        try {
            //开始执行Concurrent任务直到Scheduler要求我们让步
            workLoopConcurrent();
            break;
        } catch (thrownValue) {
            handleError(root, thrownValue);
        }
    } while (true);
    ...
}

function handleError(root, thrownValue) {
    ...
      throwException(
        root,
        workInProgress.return,
        workInProgress,
        thrownValue,
        renderExpirationTime,
      );
      workInProgress = completeUnitOfWork(workInProgress);
   ...
}

throwException

do {
    switch (workInProgress.tag) {
      ....
      case ClassComponent:
        // Capture and retry
        const errorInfo = value;
        const ctor = workInProgress.type;
        const instance = workInProgress.stateNode;
        if (
          (workInProgress.effectTag & DidCapture) === NoEffect &&
          (typeof ctor.getDerivedStateFromError === 'function' ||
            (instance !== null &&
              typeof instance.componentDidCatch === 'function' &&
              !isAlreadyFailedLegacyErrorBoundary(instance)))
        ) {
          workInProgress.effectTag |= ShouldCapture;
          workInProgress.expirationTime = renderExpirationTime;
          // Schedule the error boundary to re-render using updated state
          const update = createClassErrorUpdate(
            workInProgress,
            errorInfo,
            renderExpirationTime,
          );
          enqueueCapturedUpdate(workInProgress, update);
          return;
        }
    }
    ...
}
    

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

2. Вторая часть состоит в том, чтобы обойти все родительские узлы текущего аномального узла и определить тип каждого узла, в основном два типа, упомянутых выше.Здесь мы сосредоточимся на типе ClassComponent, чтобы определить, является ли узел аномальным граничным компонентом ( путем оценки того, существует он или нет) функция жизненного цикла componentDidCatch и т. д.), если обнаружен аномальный граничный компонент, вызовите функцию createClassErrorUpdate для создания нового обновления и поместите обновление в очередь аномальных обновлений этого узла. обновления, работа по обновлению в этой очереди будет обновлена.

этап фиксации

ReactFiberWorkLoop вfinishConcurrentRenderзнак равноcommitRootзнак равноcommitRootImplзнак равноcaptureCommitPhaseError

Коммит разделен на несколько подэтапов, на каждом этапе есть вызов try catch для захватаCommitPhaseError.

  1. Pre-mutate фаза: мы считываем состояние основного дерева перед мутацией, здесь вызывается getSnapshotBeforeUpdate
  2. Этап мутации: на этом этапе мы меняем основное дерево, завершая преобразование дерева WIP в текущее дерево.
  3. фаза стиля: вызвать эффект, прочитанный из измененного основного дерева
export function captureCommitPhaseError(sourceFiber: Fiber, error: mixed) {
  if (sourceFiber.tag === HostRoot) {
    // Error was thrown at the root. There is no parent, so the root
    // itself should capture it.
    captureCommitPhaseErrorOnRoot(sourceFiber, sourceFiber, error);
    return;
  }

  let fiber = sourceFiber.return;
  while (fiber !== null) {
    if (fiber.tag === HostRoot) {
      captureCommitPhaseErrorOnRoot(fiber, sourceFiber, error);
      return;
    } else if (fiber.tag === ClassComponent) {
      const ctor = fiber.type;
      const instance = fiber.stateNode;
      if (
        typeof ctor.getDerivedStateFromError === 'function' ||
        (typeof instance.componentDidCatch === 'function' &&
          !isAlreadyFailedLegacyErrorBoundary(instance))
      ) {
        const errorInfo = createCapturedValue(error, sourceFiber);
        const update = createClassErrorUpdate(
          fiber,
          errorInfo,
          // TODO: This is always sync
          Sync,
        );
        enqueueUpdate(fiber, update);
        const root = markUpdateTimeFromFiberToRoot(fiber, Sync);
        if (root !== null) {
          ensureRootIsScheduled(root);
          schedulePendingInteractions(root, Sync);
        }
        return;
      }
    }
    fiber = fiber.return;
  }
}

Функция CaptureCommitPhaseError аналогична throwException в предыдущей части: она проходит через все родительские узлы текущего узла исключения, находит граничный компонент исключения (компонент с функцией жизненного цикла componentDidCatch), создает новое обновление и вызывает функцию жизненного цикла componentDidCatch. компонента в update.callback .

Внимательные друзья должны заметить, что когда throwException и captureCommitPhaseError обходят узлы, они начинают обход с родительского узла узла исключения, поэтому захват исключения обычно обертывается граничным компонентом исключения с componentDidCatch или getDerivedStateFromError, который не может захватывать и обрабатывать саму ошибку.

Крюк, связанный

Компонент функции и компонент класса

Недостатки компонента Class

  1. Сложно повторно использовать логику состояния: сложно повторно использовать логику состояния между компонентами.Может быть необходимо использовать реквизиты рендеринга (свойства рендеринга) или HOC (компоненты более высокого порядка), но будь то свойства рендеринга или компоненты более высокого порядка, это будет в оригинале. Слой родительского контейнера (обычно элемент div) обернут вокруг компонента, что приводит к сложному и сложному поддержанию иерархической избыточности:
  2. Смешивание нерелевантной логики в функциях жизненного цикла (например, регистрация событий и другой логики в componentDidMount, размонтирование событий в componentWillUnmount, такая разрозненная запись, легко написать ошибки) Компоненты класса полны правильных состояний доступа и обработки, что затрудняет разделение компоненты на более мелкие компоненты
  3. это указывает на проблему: когда родительский компонент передает функцию дочернему компоненту, он должен связать это

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

преимущество крючков

  • Три проблемы, которые могут оптимизировать компоненты класса
  • Возможность повторного использования логики состояния без изменения структуры компонента (пользовательские хуки)
  • Возможность разделения взаимосвязанных частей компонента на более мелкие функции (например, настройка подписок или запрос данных).
  • Разделение ответственности за побочные эффекты: побочные эффекты относятся к логике, которая не возникает в процессе преобразования данных в представление, например запросы ajax, доступ к собственным элементам dom, локальные постоянные кэши, события привязки/отвязки, добавление подписок, настройка таймеры, журнал регистрации и т. д. В прошлом эти побочные эффекты были записаны в функции жизненного цикла компонента класса. В то время как useEffect будет выполняться после завершения рендеринга, useLayoutEffect будет выполняться после макета браузера и перед отрисовкой.

захват реквизитов и захват значений значений

capture props

class ProfilePage extends React.Component {
  showMessage = () => {
    alert("Followed " + this.props.user);
  };

  handleClick = () => {
    setTimeout(this.showMessage, 3000);
  };

  render() {
    return <button onClick={this.handleClick}>Follow</button>;
  }
}
function ProfilePage(props) {
  const showMessage = () => {
    alert("Followed " + props.user);
  };

  const handleClick = () => {
    setTimeout(showMessage, 3000);
  };

  return <button onClick={handleClick}>Follow</button>;
}

Оба компонента описывают одинаковую логику: через 3 секунды после нажатия кнопки оповещают родителя о входящем имени пользователя.

Итак, реквизиты, описанные в документации React, не являются неизменяемыми данными? Почему он все еще меняется во время выполнения?

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

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

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

Однако получение this.props в обратном вызове setTimeout нарушит эту ассоциацию и потеряет привязку к конкретному рендеру, поэтому он также потеряет правильные реквизиты.

А функциональный компонент не имеет синтаксиса this.props, поэтому реквизиты всегда неизменяемы.

тестовый адрес

захватить значение в хук

function MessageThread() {
  const [message, setMessage] = useState("");

  const showMessage = () => {
    alert("You said: " + message);
  };

  const handleSendClick = () => {
    setTimeout(showMessage, 3000);
  };

  const handleMessageChange = e => {
    setMessage(e.target.value);
  };

  return (
    <>
      <input value={message} onChange={handleMessageChange} />
      <button onClick={handleSendClick}>Send</button>
    </>
  );
}

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

const lastest = useRef("");

const showMessage = () => {
    alert("You said: " + lastest.current);
};

const handleSendClick = () => {
    setTimeout(showMessage, 3000);
};

const handleMessageChange = e => {
    lastest.current = e.target.value;
};

тестовый адрес

Принцип реализации хуков

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

React зависит от порядка вызова хуков

Трижды в день

function Form() {
  const [hero, setHero] = useState('iron man');
  if(hero){
    const [surHero, setSurHero] = useState('Captain America');
  }
  const [nbHero, setNbHero] = useState('hulk');
  // ...
}

Давайте посмотрим, как реализован наш useState.

// useState 源码中的链表实现
import React from 'react';
import ReactDOM from 'react-dom';

let firstWorkInProgressHook = {memoizedState: null, next: null};
let workInProgressHook;

function useState(initState) {
    let currentHook = workInProgressHook.next ? workInProgressHook.next : {memoizedState: initState, next: null};

    function setState(newState) {
        currentHook.memoizedState = newState;
        render();
    }
	
	// 假如某个 useState 没有执行,会导致Next指针移动出错,数据存取出错
    if (workInProgressHook.next) {
        // 这里只有组件刷新的时候,才会进入
        // 根据书写顺序来取对应的值
        // console.log(workInProgressHook);
        workInProgressHook = workInProgressHook.next;
    } else {
        // 只有在组件初始化加载时,才会进入
        // 根据书写顺序,存储对应的数据
        // 将 firstWorkInProgressHook 变成一个链表结构
        workInProgressHook.next = currentHook;
        // 将 workInProgressHook 指向 {memoizedState: initState, next: null}
        workInProgressHook = currentHook;
        // console.log(firstWorkInProgressHook);
    }
    return [currentHook.memoizedState, setState];
}

function Counter() {
    // 每次组件重新渲染的时候,这里的 useState 都会重新执行
    const [name, setName] = useState('计数器');
    const [number, setNumber] = useState(0);
    return (
        <>
            <p>{name}:{number}</p>
            <button onClick={() => setName('新计数器' + Date.now())}>新计数器</button>
            <button onClick={() => setNumber(number + 1)}>+</button>
        </>
    )
}

function render() {
    // 每次重新渲染的时候,都将 workInProgressHook 指向 firstWorkInProgressHook
    workInProgressHook = firstWorkInProgressHook;
    ReactDOM.render(<Counter/>, document.getElementById('root'));
}

render();

Давайте восстановим этот процесс Прочитав его, вы должны понимать, что текущая настройка currentHook на самом деле получается предыдущей привязкой workInProgressHook через следующий указатель, поэтому, если вызывающая последовательность прерывается в условном выражении, это приведет к тому, что следующий указатель будет указывать на отклонение , В настоящее время setState, который вы передали, не может правильно изменить соответствующее значение, потому что

Различные пользовательские перехватчики пакетов = "react-use

Почему последовательный вызов важен для React Hooks?

THE END

Во второй раз, когда я опубликовал на Nuggets, Сяо Чен также является ответным гарниром 🐔, я надеюсь обсудить и научиться с вами и перейти к продвинутой архитектуре интерфейса! Давайте полюбим клетчатку вместе

Ссылаться на:

Как и почему React Fiber использует связанные списки для обхода дерева компонентов
Архитектура React Fibre
Анализ исходного кода React — планирование асинхронных задач reactScheduler
В преддверии React 17, ретроспектива прошлого React
Это, пожалуй, самый распространенный способ открыть React Fiber (разрезка по времени)=> стратегия планирования
Всесторонний взгляд на то, что нового в React: Suspense и жизненный цикл Hooks
Разговор об архитектуре React Fiber(1)