React의 인기, 가상 DOM의 내부 및 구현은 기술 커뮤니티 및 인터뷰에서 가장 많이 논의 된 주제가 되었습니다.
이 게시물에서는 Virtual DOM에 대해 소개하고 간단한 Virtual DOM 논리를 구현하는 방법을 설명합니다.
https://www.pixelstech.net/article/1575773786-Understand-Virtual-DOM
가상 DOM을 이해하는 방법
초기에는 프론트 엔드 개발자가 데이터 상태 변경 (일반적으로 AJAX 호출 후)을 기반으로 웹 페이지 보기를 업데이트했습니다. 그러나 업데이트가 빈번한 경우 페이지 리플 로우 및 다시 그리기를 유발하여 페이지가 멈출 수 있으므로 성능이 저하됩니다.
따라서 사람들은 전체 노드 트리를 업데이트하는 대신 실제로 업데이트 되는 노드를 업데이트하는 솔루션을 제공합니다. 이 프로세스는 기존 노드 트리와 업데이트 된 노드 트리를 비교하여 차이점을 찾은 다음 업데이트를 수행합니다.
실제 DOM 표현을 사용하는 대신 사람들은 JS 객체를 사용하여 노드를 나타내며 이러한 종류의 비교를 수행합니다. 이것은 가상 DOM 개념이 그림에 나오는 것입니다. DOM과 JS 사이의 계층입니다.
가상 DOM을 나타내는 방법
가상 DOM은 JS 객체로 표현 될 수 있습니다. ES6에서 class는 Virtual DOM 구조를 나타내는 데 사용될 수 있습니다. 기본 가상 DOM에는 tagName, 속성 및 하위 노드가 있어야 합니다.
class Element {
constructor(tagName, props, children) {
this.tagName = tagName;
this.props = props;
this.children = children;
}
}
호출을 단순화하기 위해 매번 new를 호출하는 대신 새로운 Virtual DOM 객체를 반환하도록 도우미 함수를 만들 수 있습니다.
function el(tagName, props, children) {
return new Element(tagName, props, children);
}
이제 DOM 트리 아래에 있다고 가정하십시오.
<div class="test">
<span>span1</span>
</div>
가상 DOM에서 다음과 같이 나타낼 수 있습니다.
const span = el("span", {}, ["span1"]);
const div = el("div", { class: "test" }, [span]);
나중에 Virtual DOM의 차이점을 비교할 때 실제 DOM을 업데이트 해야 하므로 렌더링 함수도 정의해야 합니다.
class Element {
constructor(tagName, props, children) {
this.tagName = tagName;
this.props = props;
this.children = children;
}
render() {
const dom = document.createElement(this.tagName);
// update attributes
Reflect.ownKeys(this.props).forEach(name =>
dom.setAttribute(name, this.props[name])
);
// recusive update child nodes
this.children.forEach(child => {
const childDom =
child instanceof Element
? child.render()
: document.createTextNode(child);
dom.appendChild(childDom);
});
return dom;
}
}
가상 DOM 트리를 비교하고 업데이트하는 방법
이제 Virtual DOM과 그 사용법이 소개되었습니다. 가상 DOM 트리에서 DOM을 비교하고 업데이트하는 방법에 대해 설명합니다. 여기에는 기본적으로 가상 DOM 트리에서 추가, 삭제 및 업데이트 작업이 포함됩니다.
이 과정에는 기본적으로 두 단계가 포함됩니다.
이를 위해 두 가지 방법이 있습니다.
비교 및 업데이트를 수행하는 두 번째 방법은 다음과 같습니다.
먼저 diff와 patch의 논리를 updateEl라는 함수에 넣습니다. 기본 정의는
/**
*
* @param {HTMLElement} $parent
* @param {Element} newNode
* @param {Element} oldNode
* @param {Number} index
*/
function updateEl($parent, newNode, oldNode, index = 0) {
// ...
}
$로 시작하는 모든 변수는 페이지의 실제 DOM 요소입니다. index는 자식 노드 $parent 요소에서 oldNode의 위치를 나타냅니다.
새 노드 추가
oldNode가 정의되어 있지 않으면 newNode가 새로 추가 된 노드이므로 DOM 요소에 추가하면 됩니다.
function updateEl($parent, newNode, oldNode, index = 0) {
if (!oldNode) {
$parent.appendChild(newNode.render());
}
}
노드 삭제
newNode가 정의되지 않은 경우 DOM 트리의 해당 색인에 노드가 없음을 의미하므로 DOM 트리에서 노드를 제거하십시오.
function updateEl($parent, newNode, oldNode, index = 0) {
if (!oldNode) {
$parent.appendChild(newNode.render());
} else if (!newNode) {
$parent.removeChild($parent.childNodes[index]);
}
}
노드 업데이트
oldNode를 newNode와 비교하는 동안 세 가지 경우가 업데이트로 간주됩니다.
이 세 가지 경우를 나타 내기 위해 세 개의 기호를 정의 해 봅시다.
const CHANGE_TYPE_TEXT = Symbol("text");
const CHANGE_TYPE_PROP = Symbol("props");
const CHANGE_TYPE_REPLACE = Symbol("replace");
속성 변경을 위해서는 속성을 대체하는 기능을 정의해야 합니다.
function replaceAttribute($node, removedAttrs, newAttrs) {
if (!$node) {
return;
}
Reflect.ownKeys(removedAttrs).forEach(attr => $node.removeAttribute(attr));
Reflect.ownKeys(newAttrs).forEach(attr =>
$node.setAttribute(attr, newAttrs[attr])
);
}
그리고 변경에 대한 사례를 확인하기 위해 다른 기능이 필요합니다.
function checkChangeType(newNode, oldNode) {
if (
typeof newNode !== typeof oldNode ||
newNode.tagName !== oldNode.tagName
) {
return CHANGE_TYPE_REPLACE;
}
if (typeof newNode === "string") {
if (newNode !== oldNode) {
return CHANGE_TYPE_TEXT;
}
return;
}
const propsChanged = Reflect.ownKeys(newNode.props).reduce(
(prev, name) => prev || oldNode.props[name] !== newNode.props[name],
false
);
if (propsChanged) {
return CHANGE_TYPE_PROP;
}
return;
}
updateEl에서는 checkChangeType의 반환 값을 기반으로 해당 업데이트를 수행하며, 반환 값이 비어 있으면 변경할 필요가 없습니다.
function updateEl($parent, newNode, oldNode, index = 0) {
let changeType = null;
if (!oldNode) {
$parent.appendChild(newNode.render());
} else if (!newNode) {
$parent.removeChild($parent.childNodes[index]);
} else if ((changeType = checkChangeType(newNode, oldNode))) {
if (changeType === CHANGE_TYPE_TEXT) {
$parent.replaceChild(
document.createTextNode(newNode),
$parent.childNodes[index]
);
} else if (changeType === CHANGE_TYPE_REPLACE) {
$parent.replaceChild(newNode.render(), $parent.childNodes[index]);
} else if (changeType === CHANGE_TYPE_PROP) {
replaceAttribute($parent.childNodes[index], oldNode.props, newNode.props);
}
}
}
재귀 diff 및 패치
위의 세 가지 경우 중 어느 것도 발생하지 않으면 현재 Virtual DOM에 업데이트가 없음을 의미하므로 이제 해당 하위로 이동하고 동일한 비교 및 업데이트를 수행해야 합니다.
function updateEl($parent, newNode, oldNode, index = 0) {
let changeType = null;
if (!oldNode) {
$parent.appendChild(newNode.render());
} else if (!newNode) {
$parent.removeChild($parent.childNodes[index]);
} else if ((changeType = checkChangeType(newNode, oldNode))) {
if (changeType === CHANGE_TYPE_TEXT) {
$parent.replaceChild(
document.createTextNode(newNode),
$parent.childNodes[index]
);
} else if (changeType === CHANGE_TYPE_REPLACE) {
$parent.replaceChild(newNode.render(), $parent.childNodes[index]);
} else if (changeType === CHANGE_TYPE_PROP) {
replaceAttribute($parent.childNodes[index], oldNode.props, newNode.props);
}
} else if (newNode.tagName) {
const newLength = newNode.children.length;
const oldLength = oldNode.children.length;
for (let i = 0; i < newLength || i < oldLength; ++i) {
updateEl(
$parent.childNodes[index],
newNode.children[i],
oldNode.children[i],
i
);
}
}
}
가상 DOM에 대한 실제 코드 구현은 이러한 성숙한 프레임 워크에서 훨씬 더 복잡합니다. 위의 내용은 Virtual DOM의 작동 방식에 대한 간단한 설명이며 Virtual DOM에 대한 일반적인 아이디어를 제공해야 합니다.
등록된 댓글이 없습니다.