作者:百度外卖 程亚杰 李显 卢培鹏
转载请标明出处
источник
В процессе фронтенд-разработки фактором, оказывающим наибольшее влияние на производительность, является перестановка и перерисовка DOM.React, как лидер фронтенд-фреймворка, принимает идею Virtual DOM, чтобы эффективно решать проблема накладных расходов на обновление DOM, которые не только улучшают DOM. Эффективность работы еще больше способствовала формированию и совершенствованию разработки компонентов, управляемых данными. Как только мы привыкнем к разработке, управляемой данными, если нам потребуется использовать явные манипуляции с DOM для разработки, степень злоупотребления ничем не отличается от продажи билетов, чтобы вернуться домой во время Весеннего фестиваля, и нам придется преодолевать большие расстояния, чтобы страдать.
Основная идея VirtualDOM заключается в моделировании древовидной структуры DOM, создании данных узла в памяти для сохранения отображенной информации DOM, а когда представление необходимо обновить из-за взаимодействия и других факторов, результат разности получается путем сравнения сначала данные узла, а затем снова Это похоже на создание параллельного мира в памяти Каждый узел и атрибутивные данные дерева DOM в браузере имеют другую версию виртуального дерева DOM в этом параллельном мире Все сложно Извилистая логика обновления обрабатывается в VirtualDOM в параллельном мире, и только окончательный результат обновления отправляется в дерево DOM в браузере для выполнения, что позволяет избежать бремени избыточных и тривиальных операций с деревом DOM, тем самым эффективно повышая производительность.
Если вы уже являетесь ветераном проекта и умеете использовать vue или react, эта статья поможет вам изучить принципы работы, лежащие в основе обновления представлений этих интерфейсных фреймворков, и вы сможете в определенной степени освоить основные алгоритмы VirtualDOM. даже если вы еще не пользовались этими инструментами, управляемыми данными, прочитав эту статью, вы также поймете, как некоторые современные интерфейсные фреймворки оказывают существенное влияние на модель разработки. В то же время эта статья также является кратким изложением нашего изучения соответствующих знаний.Ошибки неизбежны.Пожалуйста, поправьте меня и с нетерпением жду вашего руководства.
Битва за эффективность различий
VirtualDOM — это важное решение для оптимизации, созданное React для устранения узких мест в производительности, связанных с перестановкой и перерисовкой DOM в сценарии разработки компонентов, и его наиболее ценная основная функция заключается в том, как идентифицировать и сохранять разницу между старой и новой структурами данных узла, то естьалгоритм сравнения. Нет никаких сомнений в том, что сложность и эффективность алгоритма сравнения являются ключевыми факторами, определяющими эффект повышения производительности VirtualDOM. Поэтому после того, как было предложено решение VirtualDOM, в сообществе продолжали появляться улучшенные алгоритмы для сравнения, ссылаясь на классическое введение Ситу Чжэнмея:
Сначала классический алгоритм обхода DFS в глубину имел сложность O(n^3) и высокую стоимость diff, а потом родился cito.js, который окажет существенное влияние на все будущие алгоритмы виртуального DOM. Он использует алгоритм, который сравнивает оба конца одновременно, что увеличивает скорость сравнения до нескольких уровней. За ним следует kivi.js, который предлагает две схемы оптимизации, основанные на cito.js, с использованием ключа для достижения отслеживания движения и применения алгоритма редактирования длины и расстояния на основе ключа (сложность алгоритма составляет O (n ^ 2)). Но такой алгоритм сравнения слишком сложен, поэтому опоздавший snabbdom упростил kivi.js, удалил алгоритм редактирования длины и расстояния и скорректировал алгоритм сравнения на обоих концах. Есть небольшая потеря в скорости, но значительно улучшается читаемость. После этого знаменитый vue2.0 интегрировал всю библиотеку snabdom.
Таким образом, текущие основные алгоритмы сравнения VirtualDOM, как правило, одинаковы.С точки зрения основных идей сравнения, методы восстановления snabbdom и react в основном одинаковы.
Дифференциальная основная стратегия
- Различие по уровню дерева (уровень за уровнем)
Поскольку структура данных diff представляет собой данные узла, которые имитируют древовидную иерархическую структуру с рендерингом DOM в качестве цели, а иерархическая структура DOM редко обновляется из-за взаимодействия в WebUI, стратегия сравнения VirtualDOM находится между старым и новым узлом. деревья.Различие получается за счет дифференциации по уровням, а не традиционного поиска по глубине обхода.Эта усовершенствованная схема, полученная смелыми допущениями, не только удовлетворяет потребности реальной сцены, но и значительно снижает сложность реализации алгоритма, т.е. улучшилось с O (n ^ 3) до O (n).
- различать по типу
Независимо от того, соответствуют ли данные узла в VirtualDOM нативному узлу DOM или компоненту в vue или react, структуры узлов поддерева различных типов узлов часто существенно различаются, и отношение входных затрат к выходным для diff будет очень высоким. Чтобы повысить эффективность сравнения, VirtualDOM сравнивает только один и тот же узел одного и того же типа.Создайте новый тип VirtualDOM, заменив старый узел.
- список различий
Когда узел сравнения находится на одном уровне, новый и старый узлы обновляются с помощью трех типов узлов: вставки, перемещения и удаления, а также предоставляют пользователю возможность установить ключевой атрибут для настройки метода сортировки по умолчанию в различии. update, в списке diff без значения ключа можно только сравнивать, обновлять, вставлять и удалять каждый элемент по порядку. В случае большого объема данных эффективность diff низкая. Если можно старательно diff на основе ключа идентификатор, вы можете быстро идентифицировать новые и старые списки.Содержание изменений для повышения эффективности diff.
Различные реализации Virtual DOM
Основываясь на трех вышеуказанных принципах сравнения, мы можем свободно выбирать конкретное решение Virtual DOM и даже практиковать различие самостоятельно. Перед этим давайте воспользуемся двумя решениями реализации Virtual DOM: snabbdom в Vue и Reconcile в React. Обучение для объектов.
vnode из снаббдом
Среди многих решений для реализации VirtuaDOM snabbdom выделяется своей эффективной реализацией, небольшим размером и гибкой масштабируемостью.Его основной код составляет всего 300+ строк, но он был применен к облегченным интерфейсным средам, таким как vue, как VirtualDOM.Основная функция реализуется.
Демонстрация, созданная с помощью snabbdom, выглядит так:
import snabbdom from 'snabbdom';
import h from 'snabbdom/h';
const patch = snabbdom.init([
require('snabbdom/modules/class'), // makes it easy to toggle classes
require('snabbdom/modules/props'), // for setting properties on DOM elements
require('snabbdom/modules/style'), // handles styling on elements with support for animations
require('snabbdom/modules/eventlisteners'), // attaches event listeners
]);
var vnode = h('div', {style: {fontWeight: 'bold'}}, 'Hello world');
patch(document.getElementById('placeholder'), vnode)
Функция h предоставляется в snabbdom в качестве основной функции для создания VirtualDOM.Три параметра, принимаемые функцией h, одновременно раскрывают три ядра, задействованные в алгоритме сравнения: тип узла, данные атрибутов и объекты дочерних узлов. Метод patch — это основная функция diff, используемая для создания начального узла DOM и обновления VirtualDOM.
function view(name) {
return h('div', [
h('input', {
props: { type: 'text', placeholder: 'Type a your name' },
on : { input: update }
}),
h('hr'),
h('div', 'Hello ' + name)
]);
}
var oldVnode = document.getElementById('placeholder');
function update(event) {
const newVnode = view(event.target.value);
oldVnode = patch(oldVnode, newVnode);
}
oldVnode = patch(oldVnode, view(''));
Выше приведено типичное приложение, которое запускает обновления VirtualDOM через входные события. В функции h вы можете не только сохранить атрибуты данных для VirtualDOM, но также можете установить функцию обратного вызова события, а также получить и обработать в ней связанные атрибуты события, такие как объект события в обратном вызове обновления. Создавая новый vnode в событии захвата, отличаясь от старого vnode и, наконец, обновляя текущий старый Vnode и показывая результат обновления пользователю, его рабочий процесс выглядит следующим образом:
Основная функция исправления в исходном коде snabbdom четко отражает стратегию VirtualDOM по различию по типу и списку отличий: если старый и новый узлы исправления оцениваются одним и тем же Vnode как не один и тот же узел, новый узел создается и вставляется и старый узел удаляется. , и sameVnode является основой для оценки того, имеют ли два узла одинаковый идентификатор ключа и является ли входящая строка селектора с типом узла и другой информацией одинаковой:
function sameVnode(vnode1, vnode2) {
return vnode1.key === vnode2.key && vnode1.sel === vnode2.sel;
}
Процесс реализации основной функции patchVnode, выполняющей новые и старые сравнения на одном и том же узле, выглядит следующим образом, где oldCh и ch должны сохранять старый текущий узел и новый узел, подлежащий обновлению:
//新的text不存在
if (isUndef(vnode.text)) {
if (isDef(oldCh) && isDef(ch)) {
if (oldCh !== ch)
updateChildren(elm, oldCh, ch, insertedVnodeQueue);
}
//旧的子节点不存在,新的存在
else if (isDef(ch)) {
//旧的text存在
if (isDef(oldVnode.text))
api.setTextContent(elm, '');
//把新的插入到elm底下
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);
}
//新的子节点不存在,旧的存在
else if (isDef(oldCh)) {
removeVnodes(elm, oldCh, 0, oldCh.length - 1);
}
//新的子节点不存在,旧的text存在
else if (isDef(oldVnode.text)) {
api.setTextContent(elm, '');
}
- Если новый узел может быть сложным узлом, а не текстовым узлом, выполните дальнейшее сравнение дочерних элементов узла: сначала определите, добавляются или удаляются дочерние элементы нового узла в целом, и если да, выполните пакетное обновление, и и старый, и новый узлы содержат список дочерних элементов, ситуация обрабатывается updateChildren
- Если и старый, и новый узлы являются текстовыми узлами и отличаются друг от друга, может быть выполнено только обновление текста.
Далее представлен основной метод diff updateChildren, при котором старый узел oldCh принимается за текущее состояние VirtualDOM, обновляется oldCh с изменением нового узла newCh для получения нового состояния VirtualDOM и записывается startIndex и endIndex старого и нового узлов в то же время сравнение, которое будет лучше, чем от одностороннего последовательного сравнения, быстрее, чтобы получить diff результаты:
- Когда startVnode и endVnode старого и нового узлов совпадают друг с другом, сравнение продолжается, а позиции startVnode и endVnode перемещаются на одно место к середине.
- Когда обнаруживается, что oldStartVnode и newEndVnode совпадают, то есть oldStartVnode становится новым конечным узлом, вставьте oldStartVnode в последнюю позицию oldEndVnode.
- Когда oldEndVnode и newStartVnode совпадают, то есть oldEndVnode становится новым головным узлом, вставьте oldEndVnode в предыдущую позицию oldStartVnode.
- Когда будет обнаружено, что в текущем newCh в oldCh нет узла, вставьте новый узел в начало oldStartVnode, В то же время значение ключа в узле будет использоваться для сопоставления, чтобы выяснить, есть ли соответствие старый узел в других местах. Если есть совпадение, будет проверен старый узел. Узел обновляется и вставляется перед текущим oldStartVnode.
- В конце этого раунда сравнения есть два случая, когда oldStartIdx > oldEndIdx, что указывает на то, что старый узел oldCh был пройден. Затем новый узел оставшегося vnode между newStartIdx и newEndIdx вызывает addVnodes и вставляет позицию родительского узла перед узлом в пакетах. addVnodes вызывает insertBefore для работы с узлом dom.Давайте посмотрим на документацию по insertBefore: parentElement.insertBefore(newElement, referenceElement) Если referenceElement равен нулю, newElement будет вставлен в конец дочернего узла. Если newElement уже находится в дереве DOM, newElement сначала удаляется из дерева DOM. Таким образом, до того, как будет установлено значение null, newElement будет вставлен в конец дочернего узла.
- Если newStartIdx > newEndIdx, это означает, что newCh пройден первым в первом раунде сравнения. В это время необходимо удалить vnodes между oldStartIdx и oldEndIdx в oldCh.Вызовите removeVnodes, чтобы удалить их из dom.
Реагировать на примирение
В исторической версии реакции процесс завершения различий между узлами данных представляет собой согласование, когда вы вызываете setState в компоненте, реакция помечает узел компонента как грязный, выполняет согласование и получает реконструированный виртуальный дом поддерева, повторно визуализируйте узел с грязной меткой в конце рабочего процесса.Если вы установитеState на корневом узле компонента, все дерево компонентов Virtual DOM будет пересоздано, но поскольку это не является прямой манипуляцией с реальным DOM, на самом деле Воздействие по-прежнему ограничено.
В переписывании React16 наиболее важным изменением было изменение базовой архитектуры на архитектуру асинхронного рендеринга под кодовым названием Fiber. По сути, Fiber — это объект POJO, а элемент React может соответствовать одному или нескольким узлам Fiber.Fiber содержит данные атрибутов, необходимые для всей работы с узлами DOM и компонентами React. Поэтому, хотя в коде React нет четкой концепции Virtual DOM, функции и механизмы Virtual DOM полностью реализованы за счет дизайна Fiber.
В дополнение к работе с Virtual DOM, реальная цель проектирования Fiber состоит в том, чтобы реализовать облегченный поток выполнения, выполняемый во внешнем интерфейсе, который разделяет пространство адресации, как и обычные потоки, но может быть запланирован собственной системой React Fiber для достижения детального рендеринга. Мощный механизм управления задачами рендеринга для разделенной, синхронизированной, прерываемой, перезапускаемой и планируемой совместной многозадачности.
Ближе к делу, хотя механизм асинхронного рендеринга Fiber почти переписывает весь процесс согласования, с помощью анализа исходного кода мы можем видеть, что идея согласования узлов в основном такая же, как и в версии до 16:
В версии 16.3.1 реакции в процессе инициализации и рендеринга страницы создается FiberNode, соответствующий структуре страницы, а дочерние узлы и одноуровневые узлы сохраняются соответственно через дочерний атрибут и атрибут одноуровневых элементов, а родительский узел отмечен атрибутом return, что удобно для обхода и модификации. Когда Файбер обновляется, он клонирует новый Файбер (называемый альтернативным) из исходного Файбера (который мы называем текущим). Побочные эффекты двух отличий Fiber записываются на альтернативе. Следовательно, при обновлении компоненту будет соответствовать не более двух волокон.После обновления альтернативный узел заменит предыдущий текущий узел и станет новым текущим узлом.
Опустим здесь сложный процесс построения Файбера, давайте сразу посмотрим на внутренний механизм, когда нужно обновить компонент, то есть после вызова метода setState в компоненте, во-первых, в узле Файбер будет установлен атрибут updateQueue соответствующий к компоненту в очередь Обновленный контент сохраняется в форме, а затем все дерево Fiber просматривается в глубину сверху вниз, чтобы найти узел Fiber, который необходимо обновить.Основой для суждения является наличие на узле обновленного контента в updateQueue.Если есть обновление, запустите хорошо известную функцию ShouldUpdateComponent, чтобы оценить, shouldUpdateComponent возвращает true, выполните функцию componentWillUpdate и решите, какой способ обновления в соответствии с его типом узла, то есть запустите механизм согласования для сравнения, если разница является компонентным узлом, запустите lifeCycle после того, как разница будет завершена, функция componentDidUpdate в файле .
const shouldUpdate = checkShouldComponentUpdate(
workInProgress,
oldProps,
newProps,
oldState,
newState,
newContext,
);
if (shouldUpdate) {
// 【译】这是为了支持react-lifecycles-compat的兼容组件
// 使用新的API的时候不能调用非安全的生命周期钩子
if (
!hasNewLifecycles &&
(typeof instance.UNSAFE_componentWillUpdate === 'function' ||
typeof instance.componentWillUpdate === 'function')
) {
//开始计时componentWillUpdate阶段
startPhaseTimer(workInProgress, 'componentWillUpdate');
//执行组件实例上的componentWillUpdate钩子
if (typeof instance.componentWillUpdate === 'function') {
instance.componentWillUpdate(newProps, newState, newContext);
}
if (typeof instance.UNSAFE_componentWillUpdate === 'function') {
instance.UNSAFE_componentWillUpdate(newProps, newState, newContext);
}
//结束计时componentWillUpdate阶段
stopPhaseTimer();
}
// 在当前工作中的Fiber打上标签,后续执行componentDidUpdate钩子
if (typeof instance.componentDidUpdate === 'function') {
workInProgress.effectTag |= Update;
}
if (typeof instance.getSnapshotBeforeUpdate === 'function') {
workInProgress.effectTag |= Snapshot;
}
} else {
// 【译】如果当前节点已经在更新中,即使我们终止了更新,仍然应该执行componentDidUpdate钩子
if (typeof instance.componentDidUpdate === 'function') {
if (
oldProps !== current.memoizedProps ||
oldState !== current.memoizedState
) {
workInProgress.effectTag |= Update;
}
}
if (typeof instance.getSnapshotBeforeUpdate === 'function') {
if (
oldProps !== current.memoizedProps ||
oldState !== current.memoizedState
) {
workInProgress.effectTag |= Snapshot;
}
}
Здесь упоминается, что после того, как мы установим состояние в компоненте, React будет рассматривать его как грязный узел.После окончания потока событий найдите грязный узел компонента и сравните его.Стоит отметить, что хотя повторный рендеринг создает новый виртуальный Дерево DOM не соприкасается с реальным DOM, и новое дерево Fiber здесь не воссоздается. Вместо этого в каждом узле Fiber задаются альтернативные и текущие свойства для хранения обновленной и текущей версии узла соответственно. , просто найдите грязный узел для генерации новый узел Fiber для обновления после повторного обхода всего дерева:
Как описано в официальной документации по реакции, когда узел необходимо обновить (shouldComponentUpdate), следующим шагом является выполнение оценки shouldComponentUpdate и процесса согласования над ним и его дочерними узлами для обновления узла. обновить логику:
Основной исходный код процесса согласования начинается с функции reconcileChildFiber.Основной метод реализации: различные процессы согласования выполняются в соответствии с типом входящих компонентов.Самым сложным является случай вызова reconcileChildrenArray для обработки входящего массива подкомпонентов. Когда функция reconcileChildrenArray начинает согласовывать новый и старый массивы дочерних узлов, она сначала сравнивает порядок индексов по умолчанию.Поскольку сам узел Fiber не устанавливает обратный указатель, React в настоящее время не использует алгоритм для сравнения обоих концов на одно и то же время, то есть каждый уровень одного и того же уровня. Узел-брат одного уровня может указывать только на следующий узел. Поэтому при нормальных обстоятельствах во время процесса сравнения react будет вызывать updateSlot только для непосредственного обновления полученных новых данных Fiber до положения старого Fiber в соответствии с его различными типами.
При последовательном сравнении, если используется updateSlot, чтобы обнаружить, что значения ключей не равны, старый узел будет заменен новым узлом.После завершения первого раунда обхода оценивается, что если новый узел имеет были пройдены, будут пройдены оставшиеся старые узлы.Удалите узлы в пакетах.Если после обхода старых узлов остались новые узлы, вставьте новые узлы в конец старых узлов в пакетах.Если обнаружено, что ключевые значения не равны в первом раунде обхода, пропустите вышеуказанные шаги и следуйте значениям ключей.Выполните обход и обновление, и, наконец, удалите элементы, которые не участвуют в вышеуказанной ситуации.Видно, что добавление ключа значений в компонентах структуры списка помогает повысить эффективность алгоритма сравнения.
Ниже приведен исходный код функции reconcileChildrenArray:
// react使用flow进行类型检查
function reconcileChildrenArray(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChildren: Array<*>,
expirationTime: ExpirationTime,
): Fiber | null {
let resultingFirstChild: Fiber | null = null;
let previousNewFiber: Fiber | null = null;
let oldFiber = currentFirstChild;
let lastPlacedIndex = 0;
let newIdx = 0;
let nextOldFiber = null;
for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
// 没有采用两端同时对比,受限于Fiber列表的单向结构
if (oldFiber.index > newIdx) {
nextOldFiber = oldFiber;
oldFiber = null;
} else {
// 指向下一个旧的兄弟节点
nextOldFiber = oldFiber.sibling;
}
// 尝试使用新的Fiber更新旧节点
const newFiber = updateSlot(
returnFiber,
oldFiber,
newChildren[newIdx],
expirationTime,
);
、 //如果在遍历中发现key值不相等的情况,则直接跳出第一轮遍历
if (newFiber === null) {
if (oldFiber === null) {
oldFiber = nextOldFiber;
}
break;
}
if (shouldTrackSideEffects) {
if (oldFiber && newFiber.alternate === null) {
// 【译】我们找到了匹配的节点,但我们并不保留当前的Fiber,所以我们需要删除当前的子节点
deleteChild(returnFiber, oldFiber);
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
// 记录上一个更新的子节点
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
oldFiber = nextOldFiber;
}
if (newIdx === newChildren.length) {
// 【译】我们已经遍历完了所有的新节点,直接删除剩余旧节点
deleteRemainingChildren(returnFiber, oldFiber);
return resultingFirstChild;
}
if (oldFiber === null) {
// 如果旧节点先遍历完,则按顺序插入剩余的新节点,这里受限于Fiber的结构比较繁琐
for (; newIdx < newChildren.length; newIdx++) {
const newFiber = createChild(
returnFiber,
newChildren[newIdx],
expirationTime,
);
if (!newFiber) {
continue;
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
// TODO: Move out of the loop. This only happens for the first run.
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
return resultingFirstChild;
}
// 【译】把子节点都设置快速查找的map映射集
const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
// 【译】使用map查找需要保存或删除的节点
for (; newIdx < newChildren.length; newIdx++) {
// 按map查找并创建新的Fiber
const newFiber = updateFromMap(
existingChildren,
returnFiber,
newIdx,
newChildren[newIdx],
expirationTime,
);
if (newFiber) {
if (shouldTrackSideEffects) {
if (newFiber.alternate !== null) {
// 【译】新的Fiber也是一个工作线程,但是如果已有当前的实例,那我们就可以复用这个Fiber,
// 我们要从列表中删除这个新的,避免准备复用的Fiber被删除
existingChildren.delete(
newFiber.key === null ? newIdx : newFiber.key,
);
}
}
// 插入当前更新位置
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
}
if (shouldTrackSideEffects)
// 【译】到此所有剩余的子节点都将被删除,加入删除队列
existingChildren.forEach(child => deleteChild(returnFiber, child));
}
//最终返回Fiber子节点列表的第一个节点
return resultingFirstChild;
}
Заключительные замечания:
Дизайн VirtualDOM является эффективным решением для повышения производительности внешнего рендеринга. Поэтому он обеспечивает основу для инструментов внешнего интерфейса, управляемых данными, и освобождает нас от утомительной работы с DOM. Различные решения VirtualDOM в основном основаны на трех различных принципы с точки зрения diff. , конкретный процесс diff рассматривает структуру данных в своем собственном рабочем контексте, эффективность алгоритма, жизненный цикл компонентов и дизайн для выбора реализации diff. Например, вышеприведенная реализация snabbdom updateChildren использует стратегию обновления, состоящую в одновременном сравнении обоих концов и перемещении в соответствии с порядком позиций, в то время как React ограничен односторонней структурой Fiber, которая обновляется путем прямой замены в последовательности. , но React оптимизирует дизайн компонентов. Механизм рабочих потоков с Fiber повышает эффективность общей производительности рендеринга, и оба предоставляют метод улучшения стратегии для сравнения на основе ключевых значений.
Дизайн VirtualDOM имеет далеко идущее влияние.Эта статья только подробно знакомит с идеей и реализацией diff в VirtualDOM.Кроме того, как создать дерево VirtualDOM, как исправлять и обновлять результаты diff и т.д. конкретные методы реализации, которые можно изучить.А механизм Fiber в React16 — это шаг вперед в асинхронном рендеринге, который заслуживает нашего постоянного внимания и изучения.
эталонное чтение
Класс алгоритма Diff:
React-less Virtual DOM with Snabbdom :functions everywhere!
Разберите исходный код snabbdom и научитесь реализовывать упрощенную библиотеку Virtual DOM.
Snabbdom - a Virtual DOM Focusing on Simplicity - Interview with Simon Friis Vindum
Опыт разработки Qunar mini React
Класс введения волокна
Как понять архитектуру React Fiber?
Обзор исходного кода React 16 Fiber
How React Fiber can make your web and mobile apps smoother and more responsive
Новый движок React — что такое React Fiber?