Skip to content

Commit

Permalink
feat: updateElement 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
nopinokio committed Dec 26, 2024
1 parent 5c136eb commit 29ec3ba
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 28 deletions.
47 changes: 24 additions & 23 deletions src/lib/createElement.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,45 @@ import { isValidVNode, isString, isNumber } from "./validCheck";
import { addEvent } from "./eventManager";

export function createElement(vNode) {
// null, undefined, boolean > 빈 텍스트 노드
if (!isValidVNode(vNode)) {
return document.createTextNode("");
}
// 텍스트와 숫자 > 텍스트 노드

if (isString(vNode) || isNumber(vNode)) {
return document.createTextNode(vNode);
}

if (Array.isArray(vNode)) {
const fragment = document.createDocumentFragment();
vNode.forEach((child) => fragment.appendChild(createElement(child)));
return fragment;
const $fragment = document.createDocumentFragment();
vNode.forEach((child) => $fragment.appendChild(createElement(child)));
return $fragment;
}

if (typeof vNode.type === "function") {
throw new Error("Function Components are not supported.");
}

const $el = document.createElement(vNode.type);
const $element = document.createElement(vNode.type);

updateAttributes($element, vNode.props ?? {});

$el.append(...vNode.children.map(createElement));
updateAttributes($el, vNode.props);
$element.append(...vNode.children.map(createElement));

return $el;
return $element;
}

/**
* DOM 요소에 속성 추가
*/
function updateAttributes(element, props) {
Object.entries(props || {})
//.filter(([attr, value]) => value)
.forEach(([attr, value]) => {
// className > class속성으로 변경
if (attr === "className") {
element.setAttribute("class", value);
} else if (attr.startsWith("on")) {
const eventType = attr == "onClick" ? "click" : attr;
addEvent(element, eventType, value);
} else {
element.setAttribute(attr, value);
}
});
function updateAttributes($element, props) {
Object.entries(props).forEach(([attr, value]) => {
if (attr === "className") {
$element.setAttribute("class", value);
} else if (attr.startsWith("on") && typeof value === "function") {
const eventType = attr.toLowerCase().slice(2);
addEvent($element, eventType, value);
} else {
$element.setAttribute(attr, value);
}
});
}
6 changes: 5 additions & 1 deletion src/lib/eventManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,11 @@ export function removeEvent(element, eventType, handler) {
}
});

if (!matchingEvent) return; //값이 없으면 그대로 종료
if (!matchingEvent) return;

// eventRegistry에서 matchingEvent를 제거 제거
const index = eventRegistry.indexOf(matchingEvent);
eventRegistry.splice(index, 1);

if (matchingEvent !== null) {
rootElement.removeEventListener(eventType, matchingEvent.handler);
Expand Down
13 changes: 11 additions & 2 deletions src/lib/renderElement.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ export function renderElement(vNode, container) {
// 최초 렌더링시에는 createElement로 DOM을 생성하고
// 이후에는 updateElement로 기존 DOM을 업데이트한다.
// 렌더링이 완료되면 container에 이벤트를 등록한다.
oldVNode = normalizeVNode(vNode);
container.appendChild(createElement(oldVNode));

if (!container.firstChild) {
oldVNode = normalizeVNode(vNode);
container.appendChild(createElement(oldVNode));
} else {
const newVNode = normalizeVNode(vNode);
updateElement(container, newVNode, oldVNode, 0);
oldVNode = newVNode;
}

setupEventListeners(container);
}
92 changes: 90 additions & 2 deletions src/lib/updateElement.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,94 @@
import { addEvent, removeEvent } from "./eventManager";
import { createElement } from "./createElement.js";

function updateAttributes(target, originNewProps, originOldProps) {}
/*
* popstate실행, 상태 변경 될 때마다 notify()를 호출, 실행하도록 createObserver의 subscribe()를 통해 등록이 돼있음
* 따라서 render함수가 호출될 때마다 newNode와 oldNode를 비교 후 업데이트 해주어야 함
* */
export function updateElement(parentElement, newNode, oldNode, index = 0) {
// 노드 삭제
if (!newNode && oldNode) {
parentElement.removeChild(parentElement.childNodes[index]);
return;
}

export function updateElement(parentElement, newNode, oldNode, index = 0) {}
// 노드 새로 추가
if (newNode && !oldNode) {
parentElement.appendChild(createElement(newNode));
return;
}

// 노드 타입 변경
if (newNode.type !== oldNode.type) {
return parentElement.replaceChild(
createElement(newNode),
parentElement.childNodes[index],
);
}

// 텍스트 노드 변경
if (typeof newNode === "string" && typeof oldNode === "string") {
if (newNode === oldNode) return;
return parentElement.replaceChild(
createElement(newNode),
parentElement.childNodes[index],
);
}

updateAttributes(
parentElement.childNodes[index],
newNode.props || {},
oldNode.props || {},
);

// 재귀적으로 자식 노드 비교
const maxLength = Math.max(newNode.children.length, oldNode.children.length);
for (let i = 0; i < maxLength; i++) {
updateElement(
parentElement.childNodes[index],
newNode.children[i],
oldNode.children[i],
i,
);
}
}

function updateAttributes(target, originNewProps, originOldProps = {}) {
// 이전 속성들 제거
for (const attr in originOldProps) {
if (attr === "children") continue;

// 이벤트 리스너인 경우
if (attr.startsWith("on")) {
const eventType = attr.toLowerCase().substring(2);
removeEvent(target, eventType, originOldProps[attr]);
continue;
}

// 새로운 props에 없는 경우 속성 제거
if (!(attr in originNewProps)) {
target.removeAttribute(attr);
}
}

// 새로운 속성 추가/업데이트
for (const attr in originNewProps) {
if (attr === "children") continue;

// 이벤트 리스너인 경우
if (attr.startsWith("on")) {
const eventType = attr.toLowerCase().substring(2);
addEvent(target, eventType, originNewProps[attr]);
continue;
}

// 값이 변경된 경우만 업데이트
if (originOldProps[attr] !== originNewProps[attr]) {
if (attr === "className") {
target.setAttribute("class", originNewProps[attr]);
} else {
target.setAttribute(attr, originNewProps[attr]);
}
}
}
}

0 comments on commit 29ec3ba

Please sign in to comment.