React Advanced Front-End Interview --- React Fiber

React.js

Прежде чем мы начнем, давайте поговорим о том, какие проблемы может помочь вам решить эта статья?

  • Почему Facebook рефакторит React
  • Что такое реактивное волокно
  • Основной алгоритм React Fiber — как реакция прерывает задачу перезапуска
  • Упрощенная версия части исходного кода, связанной с реакцией волокна.

предисловие

Исходный код этой статьи основан на React v17.0.2.

why React Fiber

  • процесс рендеринга в браузере

Начнем с механизма работы браузера. Как мы все знаем, браузер является многопроцессорным и многопоточным.Многопроцессность включает основной процесс, процесс рендеринга, процесс плагина, процесс графического процессора и т. д. на процесс рендеринга.Вот рендеринг страницы, парсинг HTML, парсинг css, где выполняется js. Процесс рендеринга включает в себя несколько потоков.На этот раз ядро ​​фокусируется на двух потоках для рендеринга страницы, потоке GUI и потоке JS.

Поток GUI отвечает за рендеринг интерфейса браузера, включая синтаксический анализ HTML, CSS, отрисовку макета и т. д. Поток js содержит механизм синтаксического анализа кода js, который мы обычно пишем, самым известным из которых является Google V8. Следует отметить, что движок js и рендеринг GUI являются взаимоисключающими, потому что JS может изменять стили HTML или CSS, и если они выполняются одновременно, рендеринг страницы будет хаотичным, поэтому при выполнении движка JS рендеринг GUI поток будет приостановлен, выполнить, как только движок JS завершит выполнение.

  • Графический рендеринг

Анимация, которую мы обычно видим, видео, по сути, быстро мелькает на изображении, обманывая человеческий глаз, заставляя людей думать, что это непрерывная анимация, чем больше изображений содержится в секунду, тем плавнее анимация и нормальные 60 изображений Это может заставить людей ощущается плавная анимация, так FPS большинства современных устройств составляет 60, то есть 60 кадров в секунду. Таким образом, Chrome должен выполнить задачу рендеринга следующего рисунка в течение 16 мс, чтобы пользователь не почувствовал пропадание кадров.

img

Поэтому, если время выполнения JS слишком велико, в основном после более чем 10 мс, пользователь почувствует очевидную задержку, что сильно повлияет на пользовательский опыт (в дальнейшем выполнение js занимает 16 мс в качестве точки разделения, а последующий рендеринг не рассчитано, фактическое исполняемое время определенно меньше 16 мс). Реализация React заключается в выполнении сравнения двух деревьев, хотя React выполнил алгоритм сравнения в соответствии с характеристиками html.оптимизация, но если уровень двух деревьев глубже, он все равно будет намного длиннее 16 мс.

React Fiber

Исходя из этого, как решить проблему? На приведенном выше рисунке React используется как js, и все синхронные операции выполняются в начале, после завершения выполнения React будут выполняться последующие парсинг html, отрисовка макета и другие операции. Проще всего подумать об оптимизации скорости выполнения JS и сокращении времени, в течение которого React занимает потоки, до менее 16 мс. При выполнении React наиболее трудоемким является алгоритм diff.React оптимизирован для html-сценариев.В отрасли нет лучшего алгоритма, способного сократить время алгоритма diff.Поэтому при уровне дерева очень глубоко, время выполнения остается прежним очень долго.

requestIdleCallback
requestIdleCallback((deadline) => {
    while ((deadline.timeRemaining() > 0 || deadline.didTimeout) && nextComponent) {
        nextComponent = performWork(nextComponent);
    }
});

Не по теме:

Если вам интересно, вы можете выполнить requestIdleCallback((deadline), который выводит параметр обратного вызова requestIdleCallback в консоль. Время, полученное на разных веб-страницах, может быть разным. Оно может даже превышать 16 мс (49,9 мс отображается на официальном сайте React ), конкретная причина может бытьздесьПроверить

Кроме того, поскольку requestIdleCallbackнекоторые ограничительные причиныRequestIdlecallback в исходном коде React, но реализован аналогичный механизм.

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

Таким образом, чтобы использовать этот метод, вам нужно изменить алгоритм diff синхронного рекурсивного обхода, основанный на вызове встроенного стека js, на асинхронное инкрементное обновление. По словам главы React, это

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

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

stack Reconciliation vs Fiber Reconciliation

stack Reconciliation

Если у нас есть следующая структура html

image-47.png

Объект js, преобразованный в компонент, подобный React, выглядит следующим образом.

const a1 = {name: 'a1'};
const b1 = {name: 'b1'};
const b2 = {name: 'b2'};
const b3 = {name: 'b3'};
const c1 = {name: 'c1'};
const c2 = {name: 'c2'};
const d1 = {name: 'd1'};
const d2 = {name: 'd2'};

a1.render = () => [b1, b2, b3];
b1.render = () => [];
b2.render = () => [c1];
b3.render = () => [c2];
c1.render = () => [d1, d2];
c2.render = () => [];
d1.render = () => [];
d2.render = () => [];

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

function walk(instance) {
  console.log(instance.name);
  let children = instance.render();
  children.forEach((child) => {
    walk(child);
  });
}
walk(a1);

Fiber Reconciliation

Реагируйте точно, как использовать обходные деревья связанного списка? Для реализации этого алгоритма сначала нам нужно посмотреть на структуру данных

  • sibling, указывает на следующий родственный узел узла
  • return, указывая на родительский узел этого узла

Или предыдущая древовидная структура dom, теперь она стала такой

image-48-2.png

Процесс построения дерева Fiber описывать не буду, рассмотрим непосредственно алгоритм обхода (сначала родительский узел, сначала глубина)

let root = fiber;
let node = fiber;
while (true) {
  if (node.child) {
    node = node.child;
    continue;
  }
  if (node === root) {
    return;
  }
  while (!node.sibling) {
    if (!node.return || node.return === root) {
      return;
    }
    node = node.return;
  }
  node = node.sibling;
}

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

Ниже представлен объект данных Fiber в исходном коде React. В конце концов, Fiber — это объект. По сравнению с объектом элемента, сгенерированным React createElement ранее, он имеет дополнительный уровень структуры данных для поддержки вышеупомянутого алгоритма обхода односвязного списка.

Структура данных волокна

Ниже приведены свойства объекта Fiber в исходном коде React, подробности вы можете увидеть непосредственно в комментариях.

function FiberNode(
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode
) {
  // Instance
  this.tag = tag; //Fiber标记是什么类型的Fiber/component,WorkTag 0-24
  this.key = key; // 唯一标识
  this.elementType = null;
  this.type = null;
  this.stateNode = null; //stateNode:class div

  // Fiber 数据结构
  this.return = null; // 父节点
  this.child = null; // 第一个子节点
  this.sibling = null; // 兄弟节点
  this.index = 0; //

  this.ref = null;

  this.pendingProps = pendingProps; //newprops
  this.memoizedProps = null; // oldProps 上次的props
  // updateQueue数据结构:
  //  {
  //   baseState: fiber.memoizedState,
  //   firstBaseUpdate: null,
  //   lastBaseUpdate: null,
  //   shared: {
  //     pending: null,
  //     interleaved: null,
  //     lanes: NoLanes,
  //   },
  //   effects: null,
  // };
  this.updateQueue = null; // 批处理队列

  this.memoizedState = null; //oldState
  this.dependencies = null;

  this.mode = mode;

  // Effects
  this.flags = NoFlags; // 标记该fiber变更方式
  this.subtreeFlags = NoFlags;
  this.deletions = null;
  // 优先级调度
  this.lanes = NoLanes; 
  this.childLanes = NoLanes;

  this.alternate = null; //work-in-progress current互为alternate
}

Эффект, приносимый клетчаткой, улучшается

  1. можно посмотреть поСравнительная демонстрация до и после реконструкции, Приносит опыт для улучшения вкуса
  2. Заложите основу для последующего режима React Concurrent.

Процесс потока волокна

Нарисуйте простую блок-схему, чтобы проиллюстрировать поток волокна.

react-fiber.png

Описание иллюстрации:

В двух методах PerformUnitOfWork и CompleteUnitOfWork react обрабатывает логику вышеуказанного алгоритма обхода Fiber и завершает логику обработки компонентов в beginwork и completeWork. В beginwork будет обработано обновление состояния, соответствующий вызов жизненного цикла на данном этапе, процесс согласования (маркировка узла Fiber с добавлением, удалением, перемещением и т.

Вы можете думать о реактивном волокне как о рождественской елке, а список эффектов — это декоративные гирлянды, висящие на рождественской елке.

Исходный код React --- слишком длинный, чтобы читать серию

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

Содержит комментарии к коду и расположение кода в репозитории React. Вы можете напрямую читать комментарии к коду без какой-либо конкретной интерпретации.

// https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L1635
function workLoopConcurrent() {
  // Perform work until Scheduler asks us to yield
  while (workInProgress !== null && !shouldYield()) {
    performUnitOfWork(workInProgress);
  }
}

performUnitOfWork

// https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L1642
function performUnitOfWork(unitOfWork: Fiber): void {
  const current = unitOfWork.alternate;
  let next;
  // 一直返回unitOfWork.child,不会处理sibling
	next = beginWork(current, unitOfWork, subtreeRenderLanes);
  unitOfWork.memoizedProps = unitOfWork.pendingProps;
  // 该fiber需要做的处理完成,返回下一个待处理的fiber
  if (next === null) {
    // 到达该链路的最底层的叶子节点,在该函数中处理sibling节点
    completeUnitOfWork(unitOfWork);
  } else {
    workInProgress = next;
  }
}

beginWork

// https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberBeginWork.old.js#L3083
function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes
): Fiber | null {
  let updateLanes = workInProgress.lanes;
	// tag有很多,这里只保留了常用的FunctionComponent和ClassComponent,后续只看updateClassComponent
  switch (workInProgress.tag) {
    case FunctionComponent: {
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === Component
          ? unresolvedProps
          : resolveDefaultProps(Component, unresolvedProps);
      return updateFunctionComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderLanes
      );
    }
    case ClassComponent: {
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === Component
          ? unresolvedProps
          : resolveDefaultProps(Component, unresolvedProps);
      // 返回值为workInProgress.child,可以在finishClassComponent中看到
      return updateClassComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderLanes
      );
    }
  }
}
function updateClassComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  nextProps: any,
  renderLanes: Lanes
) {

  const instance = workInProgress.stateNode;
  let shouldUpdate;
  // 在此阶段处理更新生命周期和批处理的更新,
  if (instance === null) {
    if (current !== null) {
      // A class component without an instance only mounts if it suspended
      // inside a non-concurrent tree, in an inconsistent state. We want to
      // treat it like a new mount, even though an empty version of it already
      // committed. Disconnect the alternate pointers.
      current.alternate = null;
      workInProgress.alternate = null;
      // Since this is conceptually a new fiber, schedule a Placement effect
      workInProgress.flags |= Placement;
    }
    // In the initial pass we might need to construct the instance.
    constructClassInstance(workInProgress, Component, nextProps);
    mountClassInstance(workInProgress, Component, nextProps, renderLanes);
    shouldUpdate = true;
  } else if (current === null) {
    // In a resume, we'll already have an instance we can reuse.
    复用之前未完成
    shouldUpdate = resumeMountClassInstance(
      workInProgress,
      Component,
      nextProps,
      renderLanes
    );
  } else {
    // 在此阶段处理生命周期和批处理的更新
    shouldUpdate = updateClassInstance(
      current,
      workInProgress,
      Component,
      nextProps,
      renderLanes
    );
  }
  const nextUnitOfWork = finishClassComponent(
    current,
    workInProgress,
    Component,
    shouldUpdate,
    hasContext,
    renderLanes
  );
  return nextUnitOfWork;
}

function finishClassComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  shouldUpdate: boolean,
  hasContext: boolean,
  renderLanes: Lanes
) {
  const instance = workInProgress.stateNode;
  // Rerender
  ReactCurrentOwner.current = workInProgress;
  let nextChildren;
	nextChildren = instance.render();
	//初始化或者执行dom diff   //ReactChildFiber.old.js
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);  
  //child
  return workInProgress.child;
}

completeUnitOfWork

// https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L1670
function completeUnitOfWork(unitOfWork: Fiber): void {
  // Attempt to complete the current unit of work, then move to the next
  // sibling. If there are no more siblings, return to the parent fiber.
  let completedWork = unitOfWork;
  do {
    const current = completedWork.alternate;
    const returnFiber = completedWork.return;

    let next;
  	// 返回值一直为null
    next = completeWork(current, completedWork, subtreeRenderLanes);
    const siblingFiber = completedWork.sibling;
    if (siblingFiber !== null) {
      // If there is more work to do in this returnFiber, do that next.
      workInProgress = siblingFiber;
      return;
    }
    // Otherwise, return to the parent
    completedWork = returnFiber;
    // Update the next thing we're working on in case something throws.
    workInProgress = completedWork;
  } while (completedWork !== null);
}

// https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberCompleteWork.old.js#L645
function completeWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
  const newProps = workInProgress.pendingProps;

  switch (workInProgress.tag) {
      case FunctionComponent:
      		bubbleProperties(workInProgress);
      		return null;
      case ClassComponent: {
      	const Component = workInProgress.type;
        if (isLegacyContextProvider(Component)) {
          popLegacyContext(workInProgress);
        }
        bubbleProperties(workInProgress);
        return null;
      }
  }

наконец

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

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

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

уведомление

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

  • Принцип планирования планировщика
  • Понимание параллельного режима
  • Принцип крючков

Ссылка на ссылку

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

Lin Clark - A Cartoon Intro to Fiber - React Conf 2017

Глава React, sebmarkbage, впервые подумал о волокне Структура данных React Fiber

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

Алгоритм планирования реагирования

О реагированных полосах

О планировании реагирования