Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[15팀 차현빈] [Chapter 1-2] 프레임워크 없이 SPA 만들기 #21

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
61 changes: 59 additions & 2 deletions src/lib/createElement.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,62 @@
//import { addEvent } from "./eventManager";

import { addEvent } from "./eventManager";

export function createElement(vNode) {}
export function createElement(vNode) {
// undifined, null, boolean은 빈텍스트 노드로 변환
if (
typeof vNode === "boolean" ||
vNode === null ||
typeof vNode === "undefined"
) {
return document.createTextNode("");
}

//string과 number은 텍스트 노드로 변환
if (typeof vNode === "string" || typeof vNode === "number") {
return document.createTextNode(vNode);
}

// vNode가 배열일 때 fragment로 리턴
if (Array.isArray(vNode)) {
const fragment = document.createDocumentFragment();
vNode.forEach((node) => {
console.log("test", node);
fragment.append(createElement(node));
});
return fragment;
}
// 컴포넌트를 createElement로 처리하려고 하면 오류 발생
if (typeof vNode === "object" && typeof vNode === "function") {
throw new Error("error");
}

//컴포넌트 정규화
const { type, props, children } = vNode;
const $el = document.createElement(type);
updateAttributes($el, props);

if (children) {
children.forEach((child) => {
const childElement = createElement(child);
$el.append(childElement);
});
}
return $el;
}

function updateAttributes($el, props) {}
function updateAttributes($el, props) {
Object.entries(props || {}).forEach(([key, value]) => {
if (key === "className") {
$el.classList = value;
return;
}
if (key.startsWith("on")) {
const eventType = key.slice(2).toLowerCase();
const handler = props[key];
addEvent($el, eventType, handler);
return;
}
$el.setAttribute(key, value);
});
}
15 changes: 14 additions & 1 deletion src/lib/createVNode.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
export function createVNode(type, props, ...children) {
return {};
console.log("log: ", children);
return {
type,
props,
children: children
.flat(Infinity)
.filter(
(value) =>
value !== undefined &&
value !== null &&
value !== false &&
value !== "",
),
};
}
74 changes: 71 additions & 3 deletions src/lib/eventManager.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,73 @@
export function setupEventListeners(root) {}
let events = new Map();

export function addEvent(element, eventType, handler) {}
let oldRoot = null;

export function removeEvent(element, eventType, handler) {}
export function setupEventListeners(root) {
// 초기등록 로직
if (oldRoot !== root) {
oldRoot = root;
events.forEach((element) => {
element.forEach((eventType, handler) => {
root.addEventListener(handler, eventType);
});
});
} //이미 등록한적이 있을 때
else {
//지우고
events.forEach((element) => {
element.forEach((eventType, handler) => {
oldRoot.removeEventListener(handler, eventType);
});
});
//다시 등록
events.forEach((element) => {
element.forEach((eventType, handler) => {
root.addEventListener(handler, eventType);
});
});
}
}

//createElement("button") === createElement("button") 인가?
// ==> nope 각각 다른 객체

// 엘리먼트 별로 핸들러를 관리하도록 해보기 ==> 통과

// 엘리먼트별로 핸들러를 관리하는데 버블링 우째처리하노...
// 각각 처리되도록 해야하나...?

export function addEvent(element, eventType, handler) {
if (!events.has(element)) {
events.set(element, new Map());
}

if (!events.get(element).has(eventType)) {
events.get(element).set(eventType, handler);
}
}

export function removeEvent(element, eventType, handler) {
// handler변수 안쓰지만 테스트 코드 바꾸기 귀찮아서 로그로 사용
console.log(handler);
//root에 저장된 모든 이벤트 삭제
events.forEach((element) => {
element.forEach((eventType, handler) => {
oldRoot.removeEventListener(handler, eventType);
});
});
// Map에 저장된 해당 이벤트 삭제
if (events.has(element) && events.get(element).has(eventType)) {
events.get(element).delete(eventType);
// 만약 삭제 후 해당 엘리먼트에 어떤 이벤트도 없으면 엘리먼트도 삭제
if (events.get(element).size === 0) {
events.delete(element);
}

// root에 이벤트 다시 등록
events.forEach((element) => {
element.forEach((eventType, handler) => {
oldRoot.addEventListener(handler, eventType);
});
});
}
}
42 changes: 41 additions & 1 deletion src/lib/normalizeVNode.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,43 @@
export function normalizeVNode(vNode) {
return vNode;
//null, undefined, boolean 처리

if (
typeof vNode === "boolean" ||
typeof vNode === "undefined" ||
vNode === null
) {
return "";
}
// 문자열, 숫자 -> 문자열로 처리
if (typeof vNode === "number" || typeof vNode === "string") {
return String(vNode);
}
// vNode가 function일 때
if (typeof vNode.type === "function") {
return normalizeVNode(
vNode.type({
...vNode.props,
children: vNode.children.filter(
(child) => child !== null && child !== undefined && child !== false,
),
}),
);
}
if (Array.isArray(vNode.children)) {
return {
type: vNode.type,
props: vNode.props,
children: vNode.children
.filter(
(child) =>
child !== null && child !== undefined && typeof child !== "boolean",
)
.map((child) => normalizeVNode(child)),
};
}

return {
...vNode,
children: normalizeVNode(vNode.children),
};
}
21 changes: 21 additions & 0 deletions src/lib/renderElement.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,29 @@ import { createElement } from "./createElement";
import { normalizeVNode } from "./normalizeVNode";
import { updateElement } from "./updateElement";

let oldNode = null;

export function renderElement(vNode, container) {
// 최초 렌더링시에는 createElement로 DOM을 생성하고

const newNode = normalizeVNode(vNode);
if (oldNode === null) {
const element = createElement(newNode);
container.append(element);
oldNode = newNode;
setupEventListeners(container);
return;
}
// 이후에는 updateElement로 기존 DOM을 업데이트한다.

if (oldNode !== null) {
updateElement(container, newNode, oldNode);
oldNode = newNode;
} else {
const element = createElement(newNode);
container.append(element);
oldNode = newNode;
}
// 렌더링이 완료되면 container에 이벤트를 등록한다.
setupEventListeners(container);
}
Loading