Друзья, знакомые с React, знают, что React поддерживает синтаксис jsx. Мы можем напрямую писать HTML-код в середине JS, а затем отображать его на странице. Если HTML-код, который мы пишем, обновляется, React также имеет сравнение виртуального DOM, и обновляет только измененный раздел без повторного рендеринга всей страницы, что значительно повышает эффективность рендеринга. В 16.x React использует метод, называемыйFiber
Архитектура улучшает пользовательский опыт, а также вводитhooks
и другие характеристики. Так в чем же принцип React?Fiber
а такжеhooks
Как это достигается? Эта статья начнется сjsx
Для начала напишите упрощенную версию React, чтобы глубоко понять принцип React.
В этой статье в основном реализованы следующие функции:
Простая оптоволоконная архитектура
Простая версия алгоритма DIFF
Простой функциональный компонент
Простой крючок:
useState
Развлекательное издание
Class
компоненты
Кодовый адрес этой статьи:GitHub.com/Денис — см....
Эта программа работает следующим образом:
JSX и createElement
Прежде чем мы напишем React Для поддержки JSX также понадобится библиотека под названиемJSXTransformer.js
, а позже работа по преобразованию JSX была интегрирована в babel, и babel также предоставилОсобенности онлайн-просмотра, вы можете увидеть преобразованный эффект, например следующий простой код:
const App =
(
<div>
<h1 id="title">Title</h1>
<a href="xxx">Jump</a>
<section>
<p>
Article
</p>
</section>
</div>
);
После преобразования babel это становится таким:
На приведенном выше снимке экрана видно, что написанный нами HTML-код был преобразован вReact.createElement
, давайте немного отформатируем приведенный выше код:
var App = React.createElement(
'div',
null,
React.createElement(
'h1',
{
id: 'title',
},
'Title',
),
React.createElement(
'a',
{
href: 'xxx',
},
'Jump',
),
React.createElement(
'section',
null,
React.createElement('p', null, 'Article'),
),
);
Из кода преобразования мы видимReact.createElement
Поддерживается несколько параметров:
- тип, который является типом узла
- config, который является свойством узла, например
id
а такжеhref
- дочерние элементы, начиная с третьего параметра, все являются дочерними элементами, то есть дочерними элементами, дочерних элементов может быть несколько, а тип может быть простым текстовым или
React.createElement
,еслиReact.createElement
, по сути это дочерний узел, и под дочерним узлом тоже могут быть дочерние узлы. так что используйтеReact.createElement
Вложенная связь реализует древовидную структуру узлов HTML.
Давайте полностью рассмотрим этот простой код страницы React:
Отображается на странице так:
На самом деле есть два места, где используется React: одно — JSX, другое —React.createElement
, другойReactDOM.render
, так что у нас есть первая цель почерка, которыйcreateElement
а такжеrender
эти два метода.
рукописный
для<h1 id="title">Title</h1>
Для такого простого узла собственный DOM также прикрепит к нему множество атрибутов и методов, так что мы находимся вcreateElement
Лучше всего преобразовать ее в более простую структуру данных, содержащую только нужные нам элементы, например такую:
{
type: 'h1',
props: {
id: 'title',
children: 'Title'
}
}
С этой структурой данных наши операции над DOM фактически могут быть преобразованы в операции над этой структурой данных, а сравнение между старой и новой DOM также может быть преобразовано в сравнение этой структуры данных, так что нам не нужно визуализировать каждую операцию.Страница, но структура данных визуализируется на странице, когда это необходимо. На самом деле это виртуальный DOM! и мыcreateElement
Это метод, ответственный за построение этого виртуального дома. Давайте реализовать его ниже:
function createElement(type, props, ...children) {
// 核心逻辑不复杂,将参数都塞到一个对象上返回就行
// children也要放到props里面去,这样我们在组件里面就能通过this.props.children拿到子元素
return {
type,
props: {
...props,
children
}
}
}
Реагируйте на приведенный выше кодcreateElement
Упрощенная версия, друзья, которым интересен исходный код, могут посмотреть здесь:GitHub.com/Facebook/Горячие…
рукописный рендер
Для приведенного выше кода мы используемcreateElement
Преобразуйте код JSX в виртуальный DOM, функция, которая фактически отображает его на странице,render
, так что нам также нужно реализовать этот метод через наше общее использованиеReactDOM.render( <App />,document.getElementById('root'));
Может быть известно, что он получает два параметра:
- Корневой компонент на самом деле является компонентом JSX, т.е.
createElement
Возвращенный виртуальный DOM- Родительский узел, где мы хотим визуализировать этот виртуальный DOM.
С этими двумя параметрами давайте реализуем следующееrender
метод:
function render(vDom, container) {
let dom;
// 检查当前节点是文本还是对象
if(typeof vDom !== 'object') {
dom = document.createTextNode(vDom)
} else {
dom = document.createElement(vDom.type);
}
// 将vDom上除了children外的属性都挂载到真正的DOM上去
if(vDom.props) {
Object.keys(vDom.props)
.filter(key => key != 'children')
.forEach(item => {
dom[item] = vDom.props[item];
})
}
// 如果还有子元素,递归调用
if(vDom.props && vDom.props.children && vDom.props.children.length) {
vDom.props.children.forEach(child => render(child, dom));
}
container.appendChild(dom);
}
Приведенный выше код является упрощенной версиейrender
способ, друзья, интересующиеся исходным кодом, могут посмотреть здесь:GitHub.com/Facebook/Горячие…
Теперь мы можем написать свой собственныйcreateElement
а такжеrender
чтобы заменить собственный метод:
Вы можете получить тот же результат рендеринга:
Зачем вам нужно волокно
Выше мы просто реализовали код, который рендерит виртуальный DOM на страницу. Эта часть работы официально называется рендерером React. Рендерер — это модуль, который третьи стороны могут реализовать сами. Существует также основной модуль, называемый reconsiler. Функции реконсилера: Всем известный diff будет вычислять, какие узлы страницы следует обновить, а затем передавать виртуальный DOM узлов, которые необходимо обновить, рендереру, который отвечает за отрисовку этих узлов на страницу. Но с этим процессом есть проблема: хотя алгоритм diff в React оптимизирован, он синхронный, а за манипуляции с DOM отвечает рендерер.appendChild
API также является синхронным, то есть при наличии большого количества узлов, которые необходимо обновить, время выполнения JS-потока может быть относительно большим, в это время браузер не будет реагировать на другие события, поскольку поток JS и поток графического интерфейса пользователя являются взаимоисключающими, страница не будет отвечать во время работы JS. Если это время слишком велико, пользователь может увидеть заикание, особенно заикание анимации будет очевидным. существуетОфициальный доклад ReactЕсть пример в , вы можете ясно увидеть заикание, вызванное этим синхронным вычислением:
Для решения этой проблемы используется Fiber.Fiber может разбивать долгосрочные задачи синхронизации на несколько небольших задач, так что браузер может отключиться, чтобы реагировать на другие события, а затем вернуться, чтобы продолжить вычисления, когда он будет пуст, так что все вычисления Процесс выглядит гораздо более плавным. Ниже приводится эффект после использования Fiber:
как разделить
Вышеупомянутое мы реализовали самиrender
Метод напрямую рекурсивно обходит все дерево vDom, если мы остановимся на каком-то шаге посередине, то при следующем вызове мы не знаем, где остановились в прошлый раз, и не знаем, с чего начать, даже если вы записываете последний конечный узел, когда он сходит, вы не знаете, какой из них выполнять следующим, поэтому древовидная структура vDom не удовлетворяет потребности паузы в середине и продолжения в следующий раз, а структура данных нуждается быть изменены. Еще одна проблема, которую необходимо решить, — когда будут выполняться разделенные задачи? Наша цель — сделать работу пользователей более плавной, поэтому нам лучше не блокировать высокоприоритетные задачи, такие как пользовательский ввод, анимация и т. д., а ждать их завершения, прежде чем производить расчеты. Итак, как мне узнать, есть ли высокоприоритетные задачи и не простаивает ли браузер? Подводя итог, чтобы волокно достигло своей цели, ему необходимо решить две проблемы:
- Новое планирование задач, когда есть задачи с высоким приоритетом, браузер будет освобожден, а выполнение продолжится, когда браузер пуст.
- Новую структуру данных можно прервать в любой момент, и при следующем входе можно продолжить выполнение
requestIdleCallback
requestIdleCallback
Это экспериментальный новый API, который называется следующим образом:
// 开启调用
var handle = window.requestIdleCallback(callback[, options])
// 结束调用
Window.cancelIdleCallback(handle)
requestIdleCallback
Получите обратный вызов, который будет вызываться при бездействии браузера, каждый вызов будет проходить вIdleDeadline
, вы можете узнать, как долго это в настоящее время бесплатно,options
Вы можете передать параметр, как долго ждать, и он будет применяться, когда браузер не пуст. Использование этого API может решить проблему планирования задач, позволяя браузеру вычислять и отображать разницу, когда он бездействует.Дополнительную информацию об использовании requestIdleCallback можно найти в документации MDN.Но этот API все еще находится в стадии эксперимента, совместимость не очень хорошая,Итак, React официально реализовал собственный набор. Эта статья будет продолжать использоватьсяrequestIdleCallback
Чтобы выполнить планирование задач, наша идея планирования задач состоит в том, чтобы разделить задачу на несколько небольших задач,requestIdleCallback
Небольшие задачи постоянно вынимаются и выполняются.Когда все задачи будут выполнены или истечет время ожидания, выполнение завершится, и следующее выполнение должно быть зарегистрировано.Полка кода выглядит следующим образом:
function workLoop(deadline) {
while(nextUnitOfWork && deadline.timeRemaining() > 1) {
// 这个while循环会在任务执行完或者时间到了的时候结束
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
// 如果任务还没完,但是时间到了,我们需要继续注册requestIdleCallback
requestIdleCallback(workLoop);
}
// performUnitOfWork用来执行任务,参数是我们的当前fiber任务,返回值是下一个任务
function performUnitOfWork(fiber) {
}
requestIdleCallback(workLoop);
вышесказанноеworkLoop
См. здесь исходный код React.
Прерываемые структуры данных оптоволокна
выше нашегоperformUnitOfWork
Это не реализовано, но из вышеприведенной структуры видно, что параметр, который он получает, является маленькой задачей, и через эту маленькую задачу он также может найти свою следующую маленькую задачу.Fiber строит такую структуру данных. Структура данных до Fiber представляет собой дерево, родительский узелchildren
Он указывает на дочерний узел, но только этот указатель не может добиться продолжения прерывания. Например, теперь у меня есть родительский узел A, у A есть три дочерних узла B, C, D. Когда я перехожу к C, он прерывается. Когда я начинаю снова, я фактически не знаю, какой из них выполнять под C, потому что я знаю только C , и нет ни указателя на его родителя, ни указателя на его родного брата. Fiber преобразует такую структуру и добавляет указатели на родительские и одноуровневые узлы:
Вышеприведенная картинка все еще из официальной речи.Вы можете видеть, что в отличие от предыдущего родительского узла, указывающего на все дочерние узлы, есть три указателя:
- child: родительский узел указывает напервый дочерний элементуказатель.
- sibling: Указывает на следующий родственный элемент после первого дочернего элемента.
- return: указатель на родительский элемент, который есть у всех дочерних элементов.
С помощью этих указателей мы можем прервать обход и возобновить его на любом элементе, например, как на рисунке выше.List
Если оно было прервано, его можно восстановить,child
Найдите его дочерние элементы, такжеreturn
Найдите его родительский элемент, если у него есть родственные узлы, его тоже можно использоватьsibling
оказаться. Структура Fiber выглядит как дерево, но в ней нет указателя на все дочерние элементы.Родительский узел указывает только на первый дочерний узел, а затем у дочернего узла есть указатели на другие дочерние узлы.На самом деле это связный список.
Реализовать волокно
Теперь мы можем реализовать Fiber самостоятельно, нам нужно преобразовать предыдущую структуру vDom в структуру данных Fiber, и в то же время нам нужно иметь возможность вернуться к следующему узлу через любой из узлов, который фактически пересекает связанный список. При обходе, начиная с корневого узла, сначала найдите дочерний элемент, если дочерний элемент существует, вернитесь напрямую, если дочернего элемента нет, найдите родственный элемент, после нахождения всех одноуровневых элементов вернитесь к родительскому элементу и затем найдите родительский элемент Sibling element. Весь процесс обхода на самом деле представляет собой обход в глубину сверху вниз, а затем последняя строка начинает обход слева направо. Например, следующий рисунок изdiv1
Если вы начинаете обход, порядок обхода должен бытьdiv1 -> div2 -> h1 -> a -> div2 -> p -> div1
. Как видно из этой последовательности, когда мыreturn
Когда родительские узлы, эти родительские узлы будут проходиться во второй раз, поэтому, когда мы пишем код,return
Родительский узел не возвращается как следующая задача, толькоsibling
а такжеchild
вернется в качестве следующей задачи.
// performUnitOfWork用来执行任务,参数是我们的当前fiber任务,返回值是下一个任务
function performUnitOfWork(fiber) {
// 根节点的dom就是container,如果没有这个属性,说明当前fiber不是根节点
if(!fiber.dom) {
fiber.dom = createDom(fiber); // 创建一个DOM挂载上去
}
// 如果有父节点,将当前节点挂载到父节点上
if(fiber.return) {
fiber.return.dom.appendChild(fiber.dom);
}
// 将我们前面的vDom结构转换为fiber结构
const elements = fiber.children;
let prevSibling = null;
if(elements && elements.length) {
for(let i = 0; i < elements.length; i++) {
const element = elements[i];
const newFiber = {
type: element.type,
props: element.props,
return: fiber,
dom: null
}
// 父级的child指向第一个子元素
if(i === 0) {
fiber.child = newFiber;
} else {
// 每个子元素拥有指向下一个子元素的指针
prevSibling.sibling = newFiber;
}
prevSibling = newFiber;
}
}
// 这个函数的返回值是下一个任务,这其实是一个深度优先遍历
// 先找子元素,没有子元素了就找兄弟元素
// 兄弟元素也没有了就返回父元素
// 然后再找这个父元素的兄弟元素
// 最后到根节点结束
// 这个遍历的顺序其实就是从上到下,从左到右
if(fiber.child) {
return fiber.child;
}
let nextFiber = fiber;
while(nextFiber) {
if(nextFiber.sibling) {
return nextFiber.sibling;
}
nextFiber = nextFiber.return;
}
}
Реагировать на исходный кодperformUnitOfWork
Посмотрите, здесь, конечно, намного сложнее, чем у нас.
Унифицированная манипуляция DOM фиксации
выше нашегоperformUnitOfWork
Структура волокна при создании операционной боковой домеappendChild
, поэтому если одновременно обновляются несколько узлов, и операция прерывается после первого узла, то мы можем видеть только первый узел, отображаемый на странице, а последующие узлы будут отображаться один за другим, когда браузер пуст. Чтобы избежать этой ситуации, мы должны собрать все операции DOM и, наконец, выполнить их единообразно, чтоcommit
. Чтобы иметь возможность записывать местоположение, нам также нужна глобальная переменнаяworkInProgressRoot
для записи корневого узла, а затем вworkLoop
Проверьте, завершена ли задача, затемcommit
:
function workLoop(deadline) {
while(nextUnitOfWork && deadline.timeRemaining() > 1) {
// 这个while循环会在任务执行完或者时间到了的时候结束
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
// 任务做完后统一渲染
if(!nextUnitOfWork && workInProgressRoot) {
commitRoot();
}
// 如果任务还没完,但是时间到了,我们需要继续注册requestIdleCallback
requestIdleCallback(workLoop);
}
Потому что мы выполняем его после того, как дерево Fiber будет полностью построено.commit
, и есть переменнаяworkInProgressRoot
Указывает на корневой узел Fiber, поэтому мы можем напрямую поместитьworkInProgressRoot
Просто возьмите его и визуализируйте рекурсивно:
// 统一操作DOM
function commitRoot() {
commitRootImpl(workInProgressRoot.child); // 开启递归
workInProgressRoot = null; // 操作完后将workInProgressRoot重置
}
function commitRootImpl(fiber) {
if(!fiber) {
return;
}
const parentDom = fiber.return.dom;
parentDom.appendChild(fiber.dom);
// 递归操作子元素和兄弟元素
commitRootImpl(fiber.child);
commitRootImpl(fiber.sibling);
}
примирение
Согласование на самом деле является операцией сравнения виртуального дерева DOM, в которой необходимо удалить ненужные узлы, обновить измененные узлы и добавить новые узлы. Чтобы иметь возможность вернуться к работе после перерыва, нам также нужна переменнаяcurrentRoot
, затем вfiber
Добавить атрибут к узлуalternate
, этот атрибут указывает на корневой узел последнего запуска, то естьcurrentRoot
.currentRoot
будет первымrender
Послеcommit
Поэтапное задание, то есть после каждого расчета текущее состояние будет записано вalternate
вверх, вы можете обновить его позжеalternate
Выньте его и сделайте diff с новым состоянием. потомperformUnitOfWork
Вам нужно добавить код согласования подэлементов, вы можете добавить функциюreconcileChildren
. Эта функция не может просто создать новый узел, а сравнить старый узел с новым узлом, логика сравнения следующая:
- Если старый и новый типы узлов совпадают, повторно используйте DOM старого узла и обновите реквизиты.
- Если типы разные и новый узел существует, создайте новый узел, чтобы заменить старый узел.
- Если тип другой, нового узла нет, есть старый узел, удалить старый узел
Обратите внимание, что операция удаления старого узла заключается в непосредственном удалении старого узла.oldFiber
Просто добавьте маркер удаления и одновременно используйте глобальную переменную.deletions
Запишите все узлы, которые необходимо удалить:
// 对比oldFiber和当前element
const sameType = oldFiber && element && oldFiber.type === element.type; //检测类型是不是一样
// 先比较元素类型
if(sameType) {
// 如果类型一样,复用节点,更新props
newFiber = {
type: oldFiber.type,
props: element.props,
dom: oldFiber.dom,
return: workInProgressFiber,
alternate: oldFiber, // 记录下上次状态
effectTag: 'UPDATE' // 添加一个操作标记
}
} else if(!sameType && element) {
// 如果类型不一样,有新的节点,创建新节点替换老节点
newFiber = {
type: element.type,
props: element.props,
dom: null, // 构建fiber时没有dom,下次perform这个节点是才创建dom
return: workInProgressFiber,
alternate: null, // 新增的没有老状态
effectTag: 'REPLACEMENT' // 添加一个操作标记
}
} else if(!sameType && oldFiber) {
// 如果类型不一样,没有新节点,有老节点,删除老节点
oldFiber.effectTag = 'DELETION'; // 添加删除标记
deletions.push(oldFiber); // 一个数组收集所有需要删除的节点
}
затем вcommit
Этап обрабатывает настоящие манипуляции с DOM, конкретные манипуляции основаны на нашихeffectTag
судить:
function commitRootImpl(fiber) {
if(!fiber) {
return;
}
const parentDom = fiber.return.dom;
if(fiber.effectTag === 'REPLACEMENT' && fiber.dom) {
parentDom.appendChild(fiber.dom);
} else if(fiber.effectTag === 'DELETION') {
parentDom.removeChild(fiber.dom);
} else if(fiber.effectTag === 'UPDATE' && fiber.dom) {
// 更新DOM属性
updateDom(fiber.dom, fiber.alternate.props, fiber.props);
}
// 递归操作子元素和兄弟元素
commitRootImpl(fiber.child);
commitRootImpl(fiber.sibling);
}
Операции DOM по замене и удалению относительно просты, а обновить атрибуты чуть хлопотнее, и нужно написать вспомогательную функциюupdateDom
реализовать:
// 更新DOM的操作
function updateDom(dom, prevProps, nextProps) {
// 1. 过滤children属性
// 2. 老的存在,新的没了,取消
// 3. 新的存在,老的没有,新增
Object.keys(prevProps)
.filter(name => name !== 'children')
.filter(name => !(name in nextProps))
.forEach(name => {
if(name.indexOf('on') === 0) {
dom.removeEventListener(name.substr(2).toLowerCase(), prevProps[name], false);
} else {
dom[name] = '';
}
});
Object.keys(nextProps)
.filter(name => name !== 'children')
.forEach(name => {
if(name.indexOf('on') === 0) {
dom.addEventListener(name.substr(2).toLowerCase(), nextProps[name], false);
} else {
dom[name] = nextProps[name];
}
});
}
updateDom
Код относительно прост для написания, и событие обрабатывает только простыеon
В начале тоже бывают проблемы с совместимостью.prevProps
а такжеnextProps
Одни и те же свойства могут быть пройдены, и есть повторяющиеся присваивания, но общий принцип остается правильным. Чтобы написать всю эту обработку, объем кода еще очень большой.
функциональный компонент
Функциональный компонент — очень распространенный компонент в React.Архитектура React перед нами фактически написана.Давайте поддержим здесь функциональный компонент. наш предыдущийfiber
на узлеtype
все типы узлов DOM, такие какh1
что, а узел функционального компонентаtype
По сути, это функция, и с таким узлом нам нужно разобраться отдельно.
Прежде всего, необходимо определить, является ли текущий узел функциональным компонентом при обновлении, и если да, тоchildren
Логика обработки будет немного другой:
// performUnitOfWork里面
// 检测函数组件
function performUnitOfWork(fiber) {
const isFunctionComponent = fiber.type instanceof Function;
if(isFunctionComponent) {
updateFunctionComponent(fiber);
} else {
updateHostComponent(fiber);
}
// ...下面省略n行代码...
}
function updateFunctionComponent(fiber) {
// 函数组件的type就是个函数,直接拿来执行可以获得DOM元素
const children = [fiber.type(fiber.props)];
reconcileChildren(fiber, children);
}
// updateHostComponent就是之前的操作,只是单独抽取了一个方法
function updateHostComponent(fiber) {
if(!fiber.dom) {
fiber.dom = createDom(fiber); // 创建一个DOM挂载上去
}
// 将我们前面的vDom结构转换为fiber结构
const elements = fiber.props.children;
// 调和子元素
reconcileChildren(fiber, elements);
}
Затем, когда мы отправляем операцию DOM, поскольку компонент функции не имеет элемента DOM, нам нужно обратить внимание на два момента:
- При получении родительского элемента DOM вам необходимо рекурсивно найти настоящий DOM онлайн.
- При удалении узла нужно рекурсивно спуститься вниз, чтобы найти настоящий узел
Давайте изменимcommitRootImpl
:
function commitRootImpl() {
// const parentDom = fiber.return.dom;
// 向上查找真正的DOM
let parentFiber = fiber.return;
while(!parentFiber.dom) {
parentFiber = parentFiber.return;
}
const parentDom = parentFiber.dom;
// ...这里省略n行代码...
if{fiber.effectTag === 'DELETION'} {
commitDeletion(fiber, parentDom);
}
}
function commitDeletion(fiber, domParent) {
if(fiber.dom) {
// dom存在,是普通节点
domParent.removeChild(fiber.dom);
} else {
// dom不存在,是函数组件,向下递归查找真实DOM
commitDeletion(fiber.child, domParent);
}
}
Теперь мы можем передать функциональный компонент:
import React from './myReact';
const ReactDOM = React;
function App(props) {
return (
<div>
<h1 id="title">{props.title}</h1>
<a href="xxx">Jump</a>
<section>
<p>
Article
</p>
</section>
</div>
);
}
ReactDOM.render(
<App title="Fiber Demo"/>,
document.getElementById('root')
);
Достичь состояния использования
useState
Это API в React Hooks, который эквивалентен предыдущемуClass Component
внутриstate
, используемый для управления внутренним состоянием компонента, и теперь у нас есть упрощенная версияReact
Теперь мы также можем попробовать реализовать этот API.
Простая версия
Мы по-прежнему начинаем с использования для достижения простейшей функции, мы обычно используемuseState
Такова, что:
function App(props) {
const [count, setCount] = React.useState(1);
const onClickHandler = () => {
setCount(count + 1);
}
return (
<div>
<h1>Count: {count}</h1>
<button onClick={onClickHandler}>Count+1</button>
</div>
);
}
ReactDOM.render(
<App title="Fiber Demo"/>,
document.getElementById('root')
);
Как видно из кода выше, нашuseState
Получает начальное значение и возвращает массив с этимstate
текущее значение и изменениеstate
метод, следует отметить, чтоApp
В качестве функционального компонента каждый разrender
будет запускаться каждый раз, то есть локальные переменные вrender
будет сбрасываться каждый раз, то нашstate
Ее нельзя использовать как локальную переменную, но ее следует хранить как глобальную переменную:
let state = null;
function useState(init) {
state = state === null ? init : state;
// 修改state的方法
const setState = value => {
state = value;
// 只要修改了state,我们就需要重新处理节点
workInProgressRoot = {
dom: currentRoot.dom,
props: currentRoot.props,
alternate: currentRoot
}
// 修改nextUnitOfWork指向workInProgressRoot,这样下次就会处理这个节点了
nextUnitOfWork = workInProgressRoot;
deletions = [];
}
return [state, setState]
}
Таким образом, мы можем использовать:
Поддержка нескольких состояний
Вышеприведенный код имеет только одинstate
переменная, если у нас есть несколькоuseState
Как это сделать? Для поддержки несколькихuseState
,нашstate
Это не может быть простое значение, мы можем рассмотреть возможность его преобразования в массив, несколькоuseState
Поместите его в этот массив в соответствии с порядком вызова и получите доступ к нему по индексу при доступе:
let state = [];
let hookIndex = 0;
function useState(init) {
const currentIndex = hookIndex;
state[currentIndex] = state[currentIndex] === undefined ? init : state[currentIndex];
// 修改state的方法
const setState = value => {
state[currentIndex] = value;
// 只要修改了state,我们就需要重新处理这个节点
workInProgressRoot = {
dom: currentRoot.dom,
props: currentRoot.props,
alternate: currentRoot
}
// 修改nextUnitOfWork指向workInProgressRoot,这样下次就会处理这个节点了
nextUnitOfWork = workInProgressRoot;
deletions = [];
}
hookIndex++;
return [state[currentIndex], setState]
}
увидеть большеuseState
Эффект:
Поддержка нескольких компонентов
Приведенный выше код, хотя мы поддерживаем несколькоuseState
, но по-прежнему есть только один набор глобальных переменных.Если есть несколько функциональных компонентов, и каждый компонент работает с этой глобальной переменной, не загрязняет ли это данные между собой? Таким образом, наши данные не могут все существовать в глобальных переменных, но должны существовать в каждой из них.fiber
Узел, тогда этот узел, занимающийся состоянием в глобальной переменной, используется для связи:
// 申明两个全局变量,用来处理useState
// wipFiber是当前的函数组件fiber节点
// hookIndex是当前函数组件内部useState状态计数
let wipFiber = null;
let hookIndex = null;
потому чтоuseState
Доступно только в функциональных компонентах, поэтому наш предыдущийupdateFunctionComponent
Его нужно инициализироватьuseState
Переменная:
function updateFunctionComponent(fiber) {
// 支持useState,初始化变量
wipFiber = fiber;
hookIndex = 0;
wipFiber.hooks = []; // hooks用来存储具体的state序列
// ......下面代码省略......
}
потому чтоhooks
очередьfiber
Узел вверх, так что мыuseState
При взятии предыдущего значения нужно начинать сfiber.alternate
Возьмите приведенное выше, полный код выглядит следующим образом:
function useState(init) {
// 取出上次的Hook
const oldHook = wipFiber.alternate && wipFiber.alternate.hooks && wipFiber.alternate.hooks[hookIndex];
// hook数据结构
const hook = {
state: oldHook ? oldHook.state : init // state是每个具体的值
}
// 将所有useState调用按照顺序存到fiber节点上
wipFiber.hooks.push(hook);
hookIndex++;
// 修改state的方法
const setState = value => {
hook.state = value;
// 只要修改了state,我们就需要重新处理这个节点
workInProgressRoot = {
dom: currentRoot.dom,
props: currentRoot.props,
alternate: currentRoot
}
// 修改nextUnitOfWork指向workInProgressRoot,这样下次requestIdleCallback就会处理这个节点了
nextUnitOfWork = workInProgressRoot;
deletions = [];
}
return [hook.state, setState]
}
Приведенный выше код показывает, что мы помещаемuseState
и хранитсяstate
используется при сопоставленииuseState
Последовательность вызова соответствуетstate
Индекс , если этот индекс не совпадает,state
неправильно, так чтоReact
Такой код не может появиться в нем:
if (something) {
const [state, setState] = useState(1);
}
Приведенный выше код не гарантирует, что каждый разsomething
удовлетворены, может привести кuseState
этот разrender
Он выполняется, но не будет выполняться в следующий раз, поэтому индексы нового и старого узлов не будут совпадать.Для такого кодаReact
Об ошибке будет сообщено напрямую:
Компоненты Mock Class с хуками
Эта функция носит исключительно развлекательный характер. Компонент Class моделируется и реализуется с помощью ранее реализованных хуков.React
Официальная реализация ha~ Мы можем написать метод для преобразования компонента класса в предыдущий компонент функции:
function transfer(Component) {
return function(props) {
const component = new Component(props);
let [state, setState] = useState(component.state);
component.props = props;
component.state = state;
component.setState = setState;
return component.render();
}
}
Затем вы можете написать класс. Этот класс выглядит как класс, который мы написали в React.state
,setState
а такжеrender
:
import React from './myReact';
class Count4 {
constructor(props) {
this.props = props;
this.state = {
count: 1
}
}
onClickHandler = () => {
this.setState({
count: this.state.count + 1
})
}
render() {
return (
<div>
<h3>Class component Count: {this.state.count}</h3>
<button onClick={this.onClickHandler}>Count+1</button>
</div>
);
}
}
// export的时候用transfer包装下
export default React.transfer(Count4);
Затем при непосредственном использовании:
<div>
<Count4></Count4>
</div>
Конечно, вы также можетеReact
Создать пустойclass Component
,ПозволятьCount4
Унаследуйте его, и это будет больше похоже на это.
Хорошо, поехали сюда, наш код готов, и полный код можно посмотреть у меня на Github.
Суммировать
- Код JSX, который мы написали, был преобразован Babel в
React.createElement
. -
React.createElement
То, что возвращается, на самом деле является виртуальной структурой DOM. -
ReactDOM.render
Метод заключается в отображении виртуального DOM на странице. - Согласование и рендеринг виртуального DOM могут быть простыми и грубо рекурсивными, но этот процесс является синхронным.Если необходимо обработать слишком много узлов, он может заблокировать пользовательский ввод и воспроизведение анимации, что приведет к зависаниям.
- Волокна - это новая функция, представленная в 16.x, которая используется для превращения синхронного примирения в асинхронные.
- Fiber преобразует структуру виртуального DOM,
父 -> 第一个子
,子 -> 兄
,子 -> 父
Эти указатели с помощью этих указателей могут найти другие узлы из любого узла Fiber. - Fiber разбивает синхронные задачи всего дерева на асинхронные структуры выполнения, которые каждый узел может выполнять независимо.
- Волокно может пройти из любого узла, обход в глубину, порядок
父 -> 子 -> 兄 -> 父
Это сверху вниз, слева направо. - Асинхронная задача фазы гармоники оптоволокна может быть небольшой, но фаза фиксации (
commit
) должен быть синхронным. из-за асинхронностиcommit
Это может позволить пользователям видеть, что узлы появляются один за другим, и это не очень хорошо. - Функциональный компонент на самом деле является этим узлом
type
это функция, которая напрямую преобразуетtype
Вы можете получить виртуальный DOM, запустив его. -
useState
Волоконный узел добавляется в массиве, каждое значение, которое соответствует массивуuseState
,useState
Порядок вызова должен соответствовать индексу этого массива, иначе будет сообщено об ошибке.
использованная литература
Замечательный классный руководитель: рукописная архитектура React’s Fiber and Hooks
Это, пожалуй, самый распространенный способ открыть React Fiber (разрезка по времени)
В конце статьи спасибо, что потратили свое драгоценное время на чтение этой статьи. Если эта статья немного поможет вам или вдохновит, пожалуйста, не скупитесь на лайки и звезды GitHub. Ваша поддержка является движущей силой для автор продолжать творить.
Добро пожаловать, чтобы обратить внимание на мой общедоступный номербольшой фронт атакиПолучите высококачественные оригиналы впервые~
Цикл статей "Передовые передовые знания":nuggets.capable/post/684490…
Адрес GitHub с исходным кодом из серии статей «Advanced Front-end Knowledge»:GitHub.com/Денис — см....