нажмитеВойдите в репозиторий отладки исходного кода React.
Эта статья — первая статья, подробно объясняющая операции React DOM.Содержание статьи происходит на этапе фиксации.
Архитектура Fiber заставляет React поддерживать два типа древовидных структур: один — это дерево Fiber, а другой — дерево DOM. При удалении узлов DOM дерево волокон также изменяется синхронно. Но будьте осторожны при выполнении операции удаления:Прежде чем выполнять другие изменения (дополнения, изменения) узлов DOM, сначала удалите узел волокна, чтобы не мешать другим операциям.Это связано с тем, что дерево волокон необходимо циклически выполнять при выполнении других операций DOM.В это время, если есть узлы волокон, которые необходимо удалить, но они не были удалены, может возникнуть путаница.
function commitMutationEffects(
firstChild: Fiber,
root: FiberRoot,
renderPriorityLevel,
) {
let fiber = firstChild;
while (fiber !== null) {
// 首先进行删除
const deletions = fiber.deletions;
if (deletions !== null) {
commitMutationEffectsDeletions(deletions, root, renderPriorityLevel);
}
// 如果删除之后的fiber还有子节点,
// 递归调用commitMutationEffects来处理
if (fiber.child !== null) {
const primarySubtreeTag = fiber.subtreeTag & MutationSubtreeTag;
if (primarySubtreeTag !== NoSubtreeTag) {
commitMutationEffects(fiber.child, root, renderPriorityLevel);
}
}
if (__DEV__) {/*...*/} else {
// 执行其他DOM操作
try {
commitMutationEffectsImpl(fiber, root, renderPriorityLevel);
} catch (error) {
captureCommitPhaseError(fiber, error);
}
}
fiber = fiber.sibling;
}
}
fiber.deletions заключается в том, что если процесс diff этапа рендеринга обнаружит, что дочерние узлы волокна необходимо удалить, они будут добавлены сюда.
commitDeletion
Функция является записью для удаления узла, она вызывается вызовомunmountHostComponents
Реализовать удаление. Прежде чем понять операцию удаления, давайте взглянем на сцену.
Существует следующее дерево волокон, узел Node (узел — это кодовое имя, не относится к конкретному узлу), который будет удален.
Fiber树
div#root
|
<App/>
|
div
|
<Parent/>
|
Delation --> Node
| ↖
| ↖
P ——————> <Child>
|
a
Из этого сценария можно сделать вывод, что при удалении узла будут удалены все узлы в поддереве под ним. Теперь непосредственно возьмем этот сценарий в качестве примера и пройдемся по процессу удаления. Этот процесс на самом делеunmountHostComponents
Как работает функция.
удалить процесс
Узел Удалить узел требует участия родительского узла DOM:
parentInstance.removeChild(child)
Итак, сначала найдите родительский узел. Процесс заключается в поиске вверх от родительского узла Node в дереве Fiber, и первый найденный собственный узел DOM является родительским узлом. В примере родительским узлом является div. После этого, начиная с узла, пройдите поддерево, которое также является деревом волокон, поэтому обход является обходом в глубину, и каждый дочерний узел удаляется.
Важно отметить, что при удалении узла петли каждый узел будет обрабатываться операцией удаления, и каждый узел здесь является узлом волокна, а не узлом DOM. Время удаления узла DOM наступает, когда узел начинает перемещаться для удаления, и когда встречается первый собственный узел DOM (HostComponent или HostText), он будет удален только после того, как будут удалены все узлы волокон его поддерева.
Выше приведено краткое описание всего процесса.Для подробного процесса необходимо прояснить обязанности и взаимосвязь вызовов нескольких ключевых функций. Тот, который удаляет волоконный узел,unmountHostComponents
Функция, удаляемый узел называется целевым узлом, и в его обязанности входит:
- Найдите родительский узел на уровне DOM целевого узла.
- Если установлено, что целевой узел является узлом собственного типа DOM, выполните 3 и 4. В противном случае сначала выгрузите себя, а затем перейдите вниз, чтобы найти узел собственного типа DOM, а затем выполните 3 и 4.
- Пройдите поддерево, чтобы выполнить выгрузку узла волокна.
- Удалить узел DOM целевого узла
Среди них операция шага 3 проходит черезcommitNestedUnmounts
Завершенный, его ответственность очень едина и ясна, то есть пройти поддерево, чтобы выгрузить узлы.
Затем, в зависимости от процесса удаления каждого узла,commitUnmount
Заканчивать. его обязанность
- Удаление ссылки
- Вызов жизненного цикла компонента класса
- Узел волокна типа HostPortal вызывается рекурсивно
unmountHostComponents
процесс дедупликации
Давайте рассмотрим конкретный процесс удаления для различных типов компонентов.
Различать категории удаленных компонентов
Вариантов типов узлов Node множество, мы берем три наиболее типичных типа (HostComponent、ClassComponent、HostPortal
) в качестве примера для иллюстрации процесса удаления соответственно.
сначала выполнитьunmountHostComponents
, он найдет родительский узел на уровне DOM, а затем обработает его в соответствии со следующими тремя типами компонентов, давайте рассмотрим их один за другим.
HostComponent
Узел HostComponent, вызовcommitNestedUnmounts
, с узлом в качестве отправной точки, пройдите поддерево и начните выгружать все подволокна.Процесс обхода - это обход в глубину.
Delation --> Node(span)
| ↖
| ↖
P ——————> <Child>
|
a
Выполнять на узлах один за другимcommitUnmount
Для выгрузки этот процесс обхода фактически одинаков для трех типов узлов.В целях экономии места он описан здесь только один раз.
Волокно узла выгружается, затем вниз, волокно p выгружается, p не имеет дочернего элемента, найти его брата<Child>
,<Child>
Волокно разгружено, найти а, и волокно а разгружено. В этот момент он достигает листового узла всего поддерева и начинает возвращаться вверх. от до<Child>
, а затем вернитесь в Node, и процесс обхода и удаления завершится.
После выгрузки всех волоконных узлов поддерева можно безопасно удалить узел DOM узла из родительского узла.
ClassComponent
Delation --> Node(ClassComponent)
|
|
span
| ↖
| ↖
P ——————> <Child>
|
a
Node — это ClassComponent, у него нет соответствующего DOM-узла, он должен быть вызван первымcommitUnmount
После выгрузки он сначала просматривает вниз, чтобы найти первый диапазон узлов собственного типа DOM, проходит поддерево с ним в качестве отправной точки, убеждается, что каждый волоконный узел выгружен, а затем удаляет диапазон из родительского узла.
HostPortal
div2(Container Of Node)
↗
div containerInfo
| ↗
| ↗
Delation --> Node(HostPortal)
|
|
span
| ↖
| ↖
P ——————> <Child>
|
a
Узел HostPortal, у него нет соответствующего узла DOM, поэтому процесс удаления в основном такой же, как и у ClassComponent, разница в том, что при удалении узла DOM первого дочернего волокна под ним он не из родительского узла на уровне DOM удаленного узла типа HostPortal. Удалите его, но удалите из containerInfo узла HostPortal.Иллюстрация - div2, поскольку HostPortal будет отображать дочерние узлы в узлах DOM, отличных от родительского компонента.
Вышеупомянутый процесс удаления трех типов узлов, Здесь стоит отметить, чтоunmountHostComponents
Когда функция выполняется для обхода поддерева для выгрузки каждого узла, как только она встречает дочерний узел типа HostPortal, она будет вызываться снова.unmountHostComponents
, возьмите его в качестве целевого узла, а затем выгрузите и удалите его и его поддеревья, что эквивалентно рекурсивному процессу.
commitUnmount
Удаление как HostComponent, так и ClassComponent вызывает commitUnmount, а также вызывается FunctionComponent. Его действие различно для трех компонентов:
- Как только useEffect вызывается в функциональном компоненте FunctionComponent, когда он выгружается, вызывается функция уничтожения useEffect. (Функция уничтожения useLayoutEffect выполняется путем вызова commitHookEffectListUnmount)
- Компонент класса ClassComponent для вызова componentWillUnmount
- HostComponent для выгрузки ссылки
function commitUnmount(
finishedRoot: FiberRoot,
current: Fiber,
renderPriorityLevel: ReactPriorityLevel,
): void {
onCommitUnmount(current);
switch (current.tag) {
case FunctionComponent:
case ForwardRef:
case MemoComponent:
case SimpleMemoComponent:
case Block: {
const updateQueue: FunctionComponentUpdateQueue | null = (current.updateQueue: any);
if (updateQueue !== null) {
const lastEffect = updateQueue.lastEffect;
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
const {destroy, tag} = effect;
if (destroy !== undefined) {
if ((tag & HookPassive) !== NoHookEffect) {
// 向useEffect的销毁函数队列里push effect
enqueuePendingPassiveHookEffectUnmount(current, effect);
} else {
// 尝试使用try...catch调用destroy
safelyCallDestroy(current, destroy);
...
}
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
return;
}
case ClassComponent: {
safelyDetachRef(current);
const instance = current.stateNode;
// 调用componentWillUnmount
if (typeof instance.componentWillUnmount === 'function') {
safelyCallComponentWillUnmount(current, instance);
}
return;
}
case HostComponent: {
// 卸载ref
safelyDetachRef(current);
return;
}
...
}
}
Суммировать
Давайте рассмотрим ключевые моменты процесса удаления:
- Когда выполняется операция удаления
- Кто является объектом удаления
- откуда удалить
Прежде чем мутация выполнит другие операции с DOM на основе узла Fiber, необходимо сначала удалить узел, чтобы гарантировать, что все узлы Fiber, зарезервированные для последующих операций, действительны. Целью удаления является узел Fiber и его поддерево и DOM-узел, соответствующий узлу Fiber.Вся траектория проходит по дереву волокон, при этом целевой узел и все дочерние узлы выгружаются, а целевой узел, соответствующий (или первый один ниже) узел DOM для удаления. Для узла собственного типа DOM он удаляется напрямую из своего родительского узла DOM.Для узла HostPortal он отображает дочерний узел во внешнем узле DOM, поэтому он будет удален из этого узла DOM. Уточнив вышеперечисленные три пункта и объединив вышеупомянутый процесс сортировки, можно постепенно прояснить контекст операции удаления.