Предупреждение длинного текста: оригинал "Оптимизация производительности React Fiber (внутренняя пробная лекция)"полная версия
Оптимизация производительности это системный проект.Если видеть только части и вводить алгоритмы,конечно чем раньше тем лучше,но с общей точки зрения введение кеша в ключевые точки может за секунды убить N кратных алгоритмов,или найти другой способ изучить суть события, тк пользователи не хотят быстро...
React16 позволил использовать новую архитектуру под названием Fiber, главная задача которой — решить проблемы с производительностью крупномасштабных проектов React, а затем легко решить некоторые из предыдущих проблем.
Болевые точки
Основные из них следующие:
- Компонент не может возвращать массив.Наиболее распространенная ситуация заключается в том, что под элементом UL можно использовать только LI, а под элементом TR можно использовать только TD или TH.В это время, когда есть компонент, формирующий список LI или TD в цикле, мы не хотим ставить еще один DIV, это нарушает семантику HTML.
- Проблема с всплывающими окнами, раньше я использовал нестабильность_renderSubtreeIntoContainer. Всплывающее окно зависит от контекста исходного дерева DOM, поэтому первым параметром этого API является экземпляр компонента, через который получается соответствующий виртуальный DOM, а затем контекст получается путем поиска по уровням вверх. Другие его параметры также работают хорошо, но этот метод не стал положительным. . .
- Обработка исключений, мы хотели бы узнать, какую ошибку компонентов даже с devtool revtool, но компонентное дерево слишком глубоко, чтобы найти все еще очень сложно. Я надеюсь, что есть способ сказать, где произошла ошибка, и ошибаться, когда у меня была возможность сделать некоторые ремонтные работы
- Популярность HOC приносит две проблемы, ведь это решение, выдвинутое сообществом и не учитывающее передачу ref и контекста вниз.
- Оптимизация производительности компонентов полностью основана на человеческой плоти, и в основном она сосредоточена на SUC, надеюсь, фреймворк что-то может, и производительность может подняться даже без SCU.
Ход решения
- 16.0 позволяет компонентам поддерживать возврат любого типа массива для решения проблемы массива; запуск createPortal API для решения проблемы всплывающих окон; запуск нового хука componentDidCatch для разделения ошибочных компонентов и граничных компонентов, каждый граничный компонент может исправить ошибку один раз нижний компонент и снова ошибка. Он передается верхнему граничному компоненту для обработки, чтобы решить проблему обработки исключений.
- 16.2 Введен компонент Fragment, который можно рассматривать как синтаксический сахар для массивов.
- 16.3 Внедрить createRef и forwardRef для решения проблемы передачи Ref в HOC и ввести новый Context API для решения проблемы передачи контекста HOC (в основном SCU Zuochong)
- Проблема с производительностью была гарантирована некоторыми внутренними механизмами начиная с версии 16.0, включая пакетные обновления и ограниченные обновления на основе разделения времени.
небольшой эксперимент
Мы можем заглянуть в идеи оптимизации React16 с помощью следующих экспериментов.
function randomHexColor(){
return "#" + ("0000"+ (Math.random() * 0x1000000 << 0).toString(16)).substr(-6);
}
setTimeout(function() {
var k = 0;
var root = document.getElementById("root");
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);
Это операция вставки с 10000 узлов, включая настройки innerHTML и стиля, которая занимает 1000 мс.
Давайте снова улучшим его, назначив время для вставки узлов и каждый раз запуская только 100 узлов, всего 100 раз, и обнаружили, что производительность исключительно хороша!
function randomHexColor() {
return "#" + ("0000" + (Math.random() * 0x1000000 << 0).toString(16)).substr(-6);
}
var root = document.getElementById("root");
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);
Причина в том, что браузер является однопоточным, он объединяет рендеринг графического интерфейса, обработку по таймеру, обработку событий, выполнение JS и удаленную загрузку ресурсов. Когда одна вещь сделана, следующая может быть сделана только тогда, когда она сделана. Если времени достаточно, браузер скомпилирует и оптимизирует наш код (JIT) и выполнит горячую оптимизацию кода, а некоторые операции DOM также обработают перекомпоновку внутри. Перекомпоновка — это черная дыра производительности, которая может привести к изменению макета большинства элементов страницы.
Как работает браузер
рендеринг -> задачи -> рендеринг -> задачи -> рендеринг -> задачи -> ....
Некоторые из этих задач контролируются, а некоторые неконтролируемые. Например, когда выполняется SettimeOut, сложно сказать, он всегда не вовремя; время загрузки ресурсов неуправляемо. Но мы можем контролировать некоторые js и позволить им отправлять выполнение. Продолжительность задач не должна быть слишком длинной, чтобы браузер успел оптимизировать код JS и исправить Reploud! Следующая картина является нашим идеальным процессом рендеринга
Сформулируйте одно предложение,Просто пусть браузер хорошо отдохнет, браузер может работать быстрее.
Как сделать так, чтобы код отключался и снова подключался
JSX — это яйцо-сюрприз, которое выполняет сразу два ваших желания:составнойа такжемаркировка. И JSX стал стандартизированным языком для компонентизации.
<div>
<Foo>
<Bar />
</Foo>
</div>
Но тегирование — это естественно вложенная структура, а это означает, что в конечном итоге она будет компилироваться в рекурсивно исполняемый код. Поэтому команда React называла планировщик до React16 планировщиком стека.В стеке нет ничего плохого, стек прост для понимания, а объем кода невелик, но его недостатки нельзя сломать и продолжить по своему желанию. Согласно нашему вышеприведенному эксперименту, мы должны выполнить повторное выполнение после разрыва, и нам нужна структура связанного списка.
Связанные списки совместимы с асинхронностью. Связному списку не нужно каждый раз входить в рекурсивную функцию при зацикливании, и он регенерирует любой контекст выполнения, переменные объекты и объекты активации.Производительность, конечно, лучше, чем у рекурсии.
Поэтому Reat16 удалось заменить рекурсивное обновление компонентов на последовательное выполнение связанных списков. Если на странице есть несколько виртуальных деревьев DOM, сохраните их корни в массиве.
ReactDOM.render(<A />, node1)
ReactDOM.render(<B />, node2)
//node1与node2不存在包含关系,那么这页面就有两棵虚拟DOM树
Если вы внимательно прочитаете исходный код, React, библиотека чистого представления, на самом деле представляет собой трехуровневую архитектуру. В React15 есть虚拟DOM层
, он отвечает только заописыватьструктура и логика;内部组件层
, они отвечают за обновление компонентов, ReactDOM.render, setState, forceUpdate — все они имеют дело с ними, позволяя вам устанавливать setState несколько раз, выполнять только один реальный рендеринг и выполнять хуки жизненного цикла ваших экземпляров компонентов в нужное время;底层渲染层
, Различные средства отображения имеют разные методы рендеринга, например, на стороне браузера, который использует узлы элементов, текстовые узлы, а на исходной стороне он вызывает oc,
Графический интерфейс Java на холсте имеет специальный метод API. . .
Виртуальный DOM транслируется с помощью JSX. Функция входа JSX — это React.createElement, в которой мало места для манипуляций. Третий по величине базовый API также очень стабилен, поэтому мы можем изменить только второй уровень.
React16 изменил уровень внутренних компонентов на структуру данных Fiber, поэтому название его архитектуры также было изменено на архитектуру Fiber. У узла волокна есть три атрибута: return, child и sibling, которые соответствуют родительскому узлу, первому потомку и его правому sibling, с ними достаточно превратить дерево в связанный список для достижения глубокого оптимизационного обхода.
Как определить количество каждого обновления
В React15 каждое обновление начинается с корневого компонента или компонента после setState и обновляет все поддерево.Единственное, что мы можем сделать, это использовать SUC в узле, чтобы отключить обновление определенной части, или оптимизировать сравнительную эффективность СУК.
React16 необходимо преобразовать виртуальный DOM в узлы Fiber.Сначала он указывает период времени, а затем обновляет столько узлов FiberNode, сколько может преобразовать за этот период времени.
Поэтому нам нужно разделить нашу логику обновления на два этапа: первый этап — преобразование виртуального DOM в Fiber, а Fiber — в экземпляр компонента или реальный DOM (без вставки DOM-дерева вставка DOM-дерева будет перекомпоновываться). Преобразование Fiber в последние два, очевидно, потребует времени, и необходимо рассчитать, сколько времени осталось. А конверсионному экземпляру нужно вызывать какие-то хуки, например, componentWillMount, если существующий экземпляр используется повторно, то он вызывает componentWillReceiveProps, shouldComponentUpdate, componentWillUpdate, что тоже займет время.
Чтобы читатели могли интуитивно понять процесс работы React Fiber, мы просто реализуем ReactDOM.render, но нет гарантии, что он запустится.
Сначала несколько простых способов:
var queue = []
ReacDOM.render = function (root, container) {
queue.push(root)
updateFiberAndView()
}
function getVdomFormQueue() {
return queue.shift()
}
function Fiber(vnode){
for(var i in vnode){
this[i] = vnode[i]
}
this.uuid = Math.random()
}
//我们简单的Fiber目前来看,只比vdom多了一个uuid属性
function toFiber(vnode){
if(!vnode.uuid){
return new Fiber(vnode)
}
return vnode
}
updateFiberAndView Чтобы реализовать разделение времени React, мы сначала имитируем его с помощью setTimeout. Пока не нужно обращать внимание на то, как реализован updateView, может быть, поставить их в другую очередь в updateComponentOrElement, а потом выйти выполнять операции insertBefore и componentDidMount!
function updateFiberAndView() {
var now = new Date - 0;
var deadline = new Date + 100;
updateView() //更新视图,这会耗时,因此需要check时间
if (new Date < deadline) {
var vdom = getVdomFormQueue()
var fiber = vdom, firstFiber
var hasVisited = {}
do {//深度优先遍历
var fiber = toFiber(fiber);//A处
if(!firstFiber){
fibstFiber = fiber
}
if (!hasVisited[fiber.uuid]) {
hasVisited[fiber.uuid] = 1
//根据fiber.type实例化组件或者创建真实DOM
//这会耗时,因此需要check时间
updateComponentOrElement(fiber);
if (fiber.child) {
//向下转换
if (newDate - 0 > deadline) {
queue.push(fiber.child)//时间不够,放入栈
break
}
fiber = fiber.child;
continue //让逻辑跑回A处,不断转换child, child.child, child.child.child
}
}
//如果组件没有children,那么就向右找
if (fiber.sibling) {
fiber = fiber.sibling;
continue //让逻辑跑回A处
}
// 向上找
fiber = fiber.return
if(fiber === fibstFiber || !fiber){
break
}
} while (1)
}
if (queue.length) {
setTimeout(updateFiberAndView, 40)
}
}
Есть цикл do while, который каждый раз тщательно рассчитывается по времени, если времени недостаточно, узлы, которые не могут быть обработаны в будущем, будут поставлены в очередь.
updateComponentOrElement — это не что иное, как это:
function updateComponentOrElement(fiber){
var {type, stateNode, props} = fiber
if(!stateNode){
if(typeof type === "string"){
fiber.stateNode = document.createElement(type)
}else{
var context = {}//暂时免去这个获取细节
fiber.stateNode = new type(props, context)
}
}
if(stateNode.render){
//执行componentWillMount等钩子
children = stateNode.render()
}else{
children = fiber.childen
}
var prev = null;
//这里只是mount的实现,update时还需要一个oldChildren, 进行key匹配,重复利用已有节点
for(var i = 0, n = children.length; i < n; i++){
var child = children[i];
child.return = fiber;
if(!prev){
fiber.child = child
}else{
prev.sibling = child
}
prev = child;
}
}
Таким образом, возврат, потомок и одноуровневый объект Fiber доступны, и обход в глубину может быть успешно выполнен.
Как запланировать время, чтобы обеспечить плавность
На самом деле сейчас есть проблема с updateFiberAndView: мы выделяем 100 мс для обновления представления и виртуального DOM, а затем выделяем еще 40 мс для других действий браузера. Если наше виртуальное DOM-дерево маленькое, нам на самом деле не нужно 100 мс; если после нашего кода у браузера есть другие дела, 40 мс может быть недостаточно. В IE10 есть новые таймеры, такие как setImmediate и requestAnimationFrame, которые позволяют нашим внешним браузерам сделать страницы более плавными.
Сам браузер также постоянно развивается: поскольку страница переходит от простого отображения к WebAPP, ему нужны новые возможности для отображения и обновления большего количества узлов.
Вот некоторые меры самопомощи:
- requestAnimationFrame
- requestIdleCallback
- web worker
- IntersectionObserver
Мы называем это вызовом управления номером кадра на уровне браузера, вызовом бездействия, многопоточным вызовом и вызовом видимой области.
requestAnimationFrame часто используется при создании анимации, и новые версии jQuery используют его. Веб-воркер выпустил несколько пакетов с самого начала angular2 и экспериментально использует их для сравнения данных. IntersectionObserver можно использовать в ListView. А requestIdleCallback — сырое лицо, и чиновникам React оно просто приглянулось.
Я только что сказал, что у updateFiberAndView есть два периода времени: один для себя и один для браузера. requestAnimationFrame может помочь нам решить второй период времени, чтобы убедиться, что все составляет 60 кадров или 75 кадров (этот номер кадра можно установить в частоте обновления дисплея операционной системы).
Давайте посмотрим, как requestIdleCallback решает эту проблему.
Его первым параметром является обратный вызов, обратный вызов имеет объект параметра, а объект имеет метод timeRemaining, эквивалентный методуnew Date - deadline
, и это высокоточные данные, более точные, чем миллисекунды, по крайней мере, сколько времени браузер планирует обновлять DOM и виртуальный DOM, нас не волнует. Не беспокойтесь о втором периоде времени, но браузер может выполнить этот обратный вызов только через 1 или 2 секунды, поэтому, чтобы быть в безопасности, мы можем установить второй параметр, чтобы он выполнялся через 300 мс после завершения обратного вызова. Верьте в браузер, потому что его пишут большие коровы, а планирование времени более эффективно, чем ваше планирование.
Итак, наш updateFiberAndView можно изменить на это:
function updateFiberAndView(dl) {
updateView() //更新视图,这会耗时,因此需要check时间
if (dl.timeRemaining() > 1) {
var vdom = getVdomFormQueue()
var fiber = vdom, firstFiber
var hasVisited = {}
do {//深度优先遍历
var fiber = toFiber(fiber);//A处
if(!firstFiber){
fibstFiber = fiber
}
if (!hasVisited[fiber.uuid]) {
hasVisited[fiber.uuid] = 1
//根据fiber.type实例化组件或者创建真实DOM
//这会耗时,因此需要check时间
updateComponentOrElement(fiber);
if (fiber.child) {
//向下转换
if (dl.timeRemaining() > 1) {
queue.push(fiber.child)//时间不够,放入栈
break
}
fiber = fiber.child;
continue //让逻辑跑回A处,不断转换child, child.child, child.child.child
}
}
//....略
} while (1)
}
if (queue.length) {
requetIdleCallback(updateFiberAndView, {
timeout:new Date + 100
}
)
}
}
На этом ограниченное обновление ReactFiber, основанное на квантовании времени, завершено. На самом деле, чтобы позаботиться о большинстве браузеров, React сам реализует requestIdleCallback.
Массовое обновление
Но команда React посчитала, что этого недостаточно, и им нужно что-то более мощное. Поскольку у некоторых компаний нет сильного спроса на синхронизацию представлений в реальном времени, и они надеются обновить представления после того, как вся логика будет запущена, поэтому существует batchedUpdates, который в настоящее время не является стабильным API, поэтому, когда вы его используете, вы должны использовать ReactDOM.unstable_batchedUpdates следующим образом.
Как это достигается? Это просто пусковой переключатель, если он включен, updateView работать не будет.
var isBatching = false
function batchedUpdates(callback, event) {
let keepbook = isBatching;
isBatching = true;
try {
return callback(event);
} finally {
isBatching = keepbook;
if (!isBatching) {
requetIdleCallback(updateFiberAndView, {
timeout:new Date + 1
}
}
}
};
function updateView(){
if(isBatching){
return
}
//更新视图
}
На самом деле, конечно, не все так просто, учитывая, что вы не умеете читать исходный код React, то можете взглянуть на то, как реализован anujs:
React также широко использует пакетные обновления для оптимизации пользовательского кода, например, setState в обратных вызовах событий и setState в обработчиках фазы фиксации (componentDidXXX).
Можно сказать,setState是对单个组件的合并渲染,batchedUpdates是对多个组件的合并渲染
. Комбинированный рендеринг — основной метод оптимизации React.
Зачем использовать оптимизированный по глубине обход
React превращает обход дерева в обход связанного списка через Fiber, но методов обхода так много, зачем использовать DSF? !
Это включает в себя очень классическую проблему передачи сообщений. Если это связь родитель-потомок, мы можем общаться через реквизиты, дочерний компонент может сохранять ссылку родителя и может вызывать родительский компонент в любое время. Если это связь между многоуровневыми компонентами или связь компонентов без отношений вмещения вызывает затруднения, React изобрел объект контекста (контекст).
Контекст начинается как пустой объект, для удобства мы называем егоunmaskedContext.
Когда он встречает компонент с методом getChildContext, этот метод создаст новый контекст, объединит его с указанным выше, а затем передаст новый контекст как unmaskedContext.
Когда он возникает составляющий компонент CONTEXTTYPES, контекст может извлекать часть компонента, который будет создан. Только часть этого контекста, мы называемmaskedContext.
Компоненты всегда вырезают кусок мяса из unmaskedContext как свой собственный контекст. жалко!
Если дочерний компонент не имеет contextTypes, то у него нет никаких свойств.
В React15 для передачи unmaskedContext большинство методов и хуков оставляют ему параметр. Но такому масштабному контексту не место в документе. В то время команда React не понимала, как управлять взаимодействием компонентов, поэтому сообщество продолжало использовать импортированный Redux для спасения жизней. Так было до тех пор, пока автор Redux не присоединился к команде React.
Существует также скрытая опасность, что SCU может сравнить его, используя maskedContext вместо unmaskedContext.
На основе этих проблем наконец появился новый Context API. Во-первых, unmaskedContext больше не шатается между методами, как раньше, есть независимый contextStack. В начале проталкивается пустой объект, а когда нужно создать экземпляр компонента, он первый. Когда к компоненту обращаются снова, он как бы извлекается из стека. Следовательно, нам нужен обход в глубину, чтобы гарантировать, что каждый узел посещается дважды.
В той же ситуации есть и контейнер, который является настоящим родительским узлом, который нам нужно использовать в виртуальном DOM элемента. В React 15 он будет загружаться в объект containerInfo и передаваться слой за слоем.
Мы знаем, что виртуальная модель DOM делится на две категории: первая — компонентная виртуальная модель DOM, тип — функция или класс, она не генерирует узлы, а создает экземпляры компонентов, а с помощью метода рендеринга создается виртуальная модель DOM следующего уровня. . Один из них — виртуальный DOM элемента, тип — имя тега, и будет создан узел DOM. StateNode (узел DOM) виртуального DOM элемента выше является контейнером виртуального DOM элемента ниже.
Этот независимый механизм стека эффективно решает проблему избыточности параметров внутренних методов.
Но есть проблема, когда первый рендеринг завершен, contextStack устанавливается пустым. Тогда мы находимся в компоненте setState виртуального DOM-дерева, как в это время должен быть получен его контекст? Решение React состоит в том, чтобы каждый раз запускать рендеринг из корня и использовать updateQueue для ускорения пропуска узлов, которые не были обновлены — каждый компонент будет создавать на нем свойство updateQueue при setState или forceUpdate. anujs сохраняет свой предыдущий unmaskedContext в экземпляре UnmaskedContext можно рассматривать как объединение всех вышеперечисленных контекстов, и один может использоваться как множественный.
Когда мы обновляем в пакетах, сколько прерывистых подкомпонентов может быть обновлено, и компонент между двумя компонентами использует SCU, возвращающий false, этот SCU следует игнорировать. Поэтому мы ссылаемся на некоторые переменные, чтобы сделать его прозрачным. Точно так же, как forceUpdate заставляет компоненты игнорировать SCU.
Зачем встряхивать крючки жизненного цикла
React делит процесс обновления виртуального DOM на два этапа: этап согласования и этап фиксации. Стадия согласования соответствует процессу сравнения более ранней версии, а стадия фиксации соответствует процессу исправления более ранней версии.
Некоторые мини-реакции, такие как preact, будут смешивать их вместе, различать во время исправления (к счастью, он использует Promise.then, чтобы убедиться, что за раз обновляется только один компонент).
Некоторые мини-реакции оптимизируются за счет уменьшения движения, поэтому они ломают голову и используют различные алгоритмы, кратчайшее расстояние редактирования, самую длинную общую подпоследовательность, самую длинную восходящую подпоследовательность. . .
На самом деле оптимизация на основе алгоритмов — это отчаянная оптимизация, как цивилизация майя осталась в каменном веке, потому что не могла найти медные рудники, и родился великий дух ремесленника, чтобы красиво полировать каменные орудия.
Причина этого в том, что алгоритм diff используется для сравнения старых и новых дочерних элементов компонентов, а дочерние элементы, как правило, не слишком длинные, что немного похоже на пушечное ядро. Более того, когда наше приложение становится очень большим, на странице десятки тысяч компонентов, и такое количество компонентов нужно диффить, каким бы отличным ни был алгоритм, он не может гарантировать, что браузер не устанет. Потому что они не ожидали, что браузер устанет, и они не ожидали, что это станет проблемой для бега на большие расстояния. Если это спринт на 100 м или забег на 1000 м, конечно, чем быстрее, тем лучше. Если это марафон, нужно учитывать сохранение физических сил, и нужно уделить внимание отдыху. Перформанс — это систематический проект.
В нашем коде休息
Это для определения времени, а затем отключить оптоволоконную цепочку.
В updateFiberAndView сначала выполняется updateView, так как обновление узла неконтролируемое, время определяется после завершения всех обновлений. И нам вообще не нужно беспокоиться об updateView, потому что updateView, по сути, находится в пакетном обновлении, в котором есть try catch. Далее обновляем узлы на основе DFS, и каждый узел должен проверять время, Этот процесс на самом деле очень боится ошибок, потому что компонент будет вызывать хук/метод (конструктор, componentWillMount, render) трижды в процессе монтирования , а компонент находится в процессе обновления.Средняя будет подгонять хук 4 раза (componentWillReceiveProps, shouldUpdate, componentWillUpdate), не всегда есть возможность обернуть каждый метод с помощью try catch, что приведет к снижению производительности. А конструктор render неизбежен, поэтому используются три willXXX.
В более ранних версиях componentWillMount и componentWillReceiveProps будут оптимизированы внутри, а множественные выполнения setState будут отложены до тех пор, пока не будет выполнена обработка слияния при рендеринге. Поэтому пользователь произвольно устанавливает состояние. Эти willXXX также позволяют пользователям произвольно манипулировать DOM. Манипуляции с DOM могут перепрошиваться, чего официалы видеть не хотят. Итак, официальный запуск getDerivedStateFromProps позволяет вам установить новое состояние в рендере, вы в основном возвращаете новый объект, и он возьмет на себя инициативу, чтобы помочь вам установить состояние. Поскольку это статический метод, вы не можете манипулировать экземпляром, что не позволяет вам манипулировать setState несколько раз. Поскольку нет экземпляра, нетinstance.refs.xxx, у вас также нет возможности манипулировать DOM. Таким образом, логика getDerivedStateFromProps должна быть очень простой, чтобы не было ни ошибки, ни ошибки, ни прерывания процесса DFS.
getDerivedStateFromProps заменил исходные методы componentWillMount и componentWillReceiveProps, а componentWillUpdate изначально был необязательным, прежде чем он стал полностью симметричным и красивым.
Даже в грядущем асинхронном обновлении фаза согласования может выполняться несколько раз перед выполнением фиксации, что также приведет к многократному выполнению хуков willXXX, нарушая их семантику, и их отказ будет необратимым.
При входе в фазу фиксации компонент имеет новый хук с именем getSnapshotBeforeUpdate, который выполняется только один раз, как и хук в фазе фиксации.
Если что-то пойдет не так, после componentDidMount/Update мы можем использовать метод componentDidCatch. Таким образом, весь процесс становится таким:
Хуки на этапе согласования не должны работать с DOM, и лучше не устанавливать состояние, мы называем этоЛегкий крючок*. Хук на этапе фиксации называетсяГрузовой крюк**.
система миссии
updateFiberAndView находится в requestIdleCallback, поэтому его время ограничено, а части DFS выделяется меньше времени, поэтому они мало что могут сделать. Как это сделать, отметьте это и оставьте на стадии коммита. Таким образом была создана система задач.
Когда каждое волокно назначается новой задаче, оно накапливает побочный эффект посредством битовых операций. sideEffect буквально означает побочный эффект, что очень тяжело на вкус FP flow, но мы понимаем его как более удобную для нашего понимания задачу.
Каждое волокно может иметь более одной задачи, например, DOM или переместить его в INSERT, вам необходимо добавить замену, нужно установить стиль, вам нужно добавить обновление.
Как добавить задачи?
fiber.effectTag |= Update
Как сделать так, чтобы одна и та же задача не добавлялась повторно?
fiber.effectTag &= ~DidCapture;
На этапе фиксации, как вы узнаете, что он содержит определенную задачу?
if(fiber.effectTag & Update){ /*操作属性*/}
В React встроено так много задач, от манипулирования DOM до обработки Ref и вызова обратного вызова. . .
Кстати, название задачи Ану основано на умножении и делении простых чисел.
Будь то битовая операция или простое число, нам нужно только убедиться, что задача той же природы, что и у волокна, выполняется только один раз.
Кроме того, система задач имеет еще одно значение, гарантируя, что некоторые задачи выполняются первыми, а некоторые задачи — раньше других. Мы называем это сортировкой задач. Это похоже на управление складом экспресс-доставки, которое можно оптимизировать только с помощью классификации. Например, операции вставки и перемещения в виртуальной модели DOM элемента должны выполняться перед всеми задачами, а операции удаления должны выполняться после componentWillUnmount. Причина, по которой эти задачи расположены в таком порядке, заключается в том, что это разумно: они были тщательно изучены экспертами и проверены общественностью в эпоху React15.
Соединённая детская структура волокна
Сиамские близнецы — это ужасный термин, и думать об этом неудобно, потому что на самом деле Fiber — это необычная структура, и до сих пор мой anujs не очень хорошо реализовал эту структуру. Волокно имеет атрибут, называемый альтернативой, вы называете его запасным колесом, суррогатом, каскадером. Вы также можете думать об этом как о ветке разработки git, а стабильная ветка — это master. Каждый раз, когда вы устанавливаетеState, появляется объект _reactInternalFiber в экземпляре компонента stateNode, который является основной ветвью, а затем немедленно копируется идентичный альтернативный объект, специально используемый для того, чтобы наступить на гром.
Альтернативный объект примет новые реквизиты, переданные сверху, а затем получит новое состояние от getDerivedStateFromProps, поэтому отрисовываются разные подкомпоненты, а подкомпоненты визуализируются снова.Постепенно разница между основным и альтернативным становится все больше и больше. subcomponent терпит неудачу, поэтому мы откатываемся к основной ветке граничного компонента.
Можно сказать, что React16 имитирует три важные операции git через структуру данных Fiber: git add, git commit, git revert.
Для размышлений о структуре сиамских близнецов, пожалуйста, обратитесь к другой моей статье.«От границ ошибок к откату к MWI», здесь не будет распространяться.
система промежуточного программного обеспечения
Говоря о промежуточных системах, вы можете быть знакомы с луковой моделью в koa и redux.
Еще в эпоху React15 уже существовала вещь под названием Transaction, которая точно такая же, как луковая модель. В исходном коде Transaction есть специальная ASCII-диаграмма, наглядно поясняющая роль Transaction.
Проще говоря, транзакция должна инкапсулировать метод, который необходимо выполнить, с помощью оболочки, а затем выполнить его с помощью метода выполнения, предоставленного транзакцией. Перед выполнением выполнить все методы инициализации в оболочке; после завершения выполнения (то есть после выполнения метода) выполнить все методы закрытия. Набор методов initialize и close называется оберткой.Как видно из примера диаграммы выше, Transaction поддерживает стекирование нескольких оболочек.
Какая польза от этой штуки? Есть по крайней мере два способа использования: при обновлении DOM собрать текущий элемент и выделение, а после обновления восстановить фокус и выделение (поскольку вставка нового узла приведет к потере фокуса, document.activeElement становится body или autoFocus, поэтому что фокус становится другим вводом, из-за чего курсор ввода, который мы печатаем, исчезает и не может быть введен нормально). При обновлении нам нужно сохранить некоторые неуправляемые компоненты, а после обновления восстановить неуправляемые компоненты (неуправляемые компоненты - это скрытая точка знаний, цель - сделать так, чтобы те элементы формы, у которых не установлен onChange, были изменены вручную. его значение ). Конечно, contextStack, Начальное стекирование и опустошение containerStack также может быть реализовано в промежуточном программном обеспечении. Промежуточное ПО распространяется по обеим сторонам пакетных обновлений, очень легко расширяемый дизайн, почему бы не использовать его!
Суммировать
React Fiber — это революция для React, которая решает проблему, связанную с тем, что проекты React в значительной степени зависят от ручной оптимизации, и обеспечивает беспрецедентную оптимизацию производительности за счет планирования времени на уровне системы. Оригинальная структура волокна обеспечивает отступление от аномальной границы и следующую отправную точку для ограниченных обновлений. Команда React обладает большим талантом, необычайной креативностью, изобретательностью и решает проблемы на более высоком уровне, что редко встречается в других командах с открытым исходным кодом. Вот почему я всегда выбираю и изучаю React.
Но, как и всем, мое первоначальное изучение исходного кода React16 было очень болезненным. Позже, посмотрев видео их команды и глубоко разобравшись в структуре time slicing и связного списка Fiber, я постепенно прояснил всю идею, и общий процесс можно скопировать без отладки по точкам останова на исходном коде React. Как говорится, читать хуже, чем писать (то есть писать ануджи, добро пожаловать в добавление звездочки,GitHub.com/Руби Лувр/…), лучше повторить отлучение другим. Отсюда и эта статья.
******************
Сознательно вознагражден: Цинь Чэн
******************