Skip to content

Commit

Permalink
가상돔 만들기, 이벤트 매니저 함수 관련 과제 완료
Browse files Browse the repository at this point in the history
  • Loading branch information
BongjoonKim committed Dec 25, 2024
1 parent cc286d7 commit 5f3ac90
Show file tree
Hide file tree
Showing 14 changed files with 489 additions and 40 deletions.
3 changes: 3 additions & 0 deletions src/components/posts/Post.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import { createVNode } from "../../lib";
import { toTimeFormat } from "../../utils/index.js";

export const Post = ({
id,
author,
time,
content,
likeUsers,
activationLike = false,
onClick,
}) => {
return (
<div className="bg-white rounded-lg shadow p-4 mb-4">
Expand All @@ -20,6 +22,7 @@ export const Post = ({
<p>{content}</p>
<div className="mt-2 flex justify-between text-gray-500">
<span
onClick={() => onClick(id)}
className={`like-button cursor-pointer${activationLike ? " text-blue-500" : ""}`}
>
좋아요 {likeUsers.length}
Expand Down
41 changes: 30 additions & 11 deletions src/components/posts/PostForm.jsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,39 @@
/** @jsx createVNode */
import { createVNode } from "../../lib";
import { globalStore } from "../../stores/index.js";

function createContent(event) {
event.preventDefault();
const formData = new FormData(event.target);
console.log("formData", formData.get("content"));
const data = {};

data.id = globalStore.getState().posts.length + 1;
data.author = globalStore.getState().currentUser.username;
data.time = Date.now();
data.content = formData.get("content");
data.likeUsers = [];
globalStore.setState({ posts: [...globalStore.getState().posts, data] });
}

export const PostForm = () => {
return (
<div className="mb-4 bg-white rounded-lg shadow p-4">
<textarea
id="post-content"
placeholder="무슨 생각을 하고 계신가요?"
className="w-full p-2 border rounded"
/>
<button
id="post-submit"
className="mt-2 bg-blue-600 text-white px-4 py-2 rounded"
>
게시
</button>
<form id={"content-form"} onSubmit={createContent}>
<textarea
id="post-content"
placeholder="무슨 생각을 하고 계신가요?"
className="w-full p-2 border rounded"
name="content"
/>
<button
id="post-submit"
className="mt-2 bg-blue-600 text-white px-4 py-2 rounded"
type={"submit"}
>
게시
</button>
</form>
</div>
);
};
46 changes: 44 additions & 2 deletions src/lib/createElement.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,47 @@
import { addEvent } from "./eventManager";

export function createElement(vNode) {}
export function createElement(vNode) {
if (vNode === undefined || vNode === null || typeof vNode === "boolean") {
return document.createTextNode("");
} else if (typeof vNode === "number" || typeof vNode === "string") {
return document.createTextNode(vNode.toString());
} else if (Array.isArray(vNode)) {
const parentComponent = document.createDocumentFragment();
vNode.forEach((vN) => parentComponent.appendChild(createElement(vN)));
return parentComponent;
}

function updateAttributes($el, props) {}
/* vNode를 컴포넌트로 바꾸는 과정입니다 */
// 일반 Object 형태의 컴포넌트인 경우
// console.log("vNode", vNode);
// vNode.type에 맞게 해당 타입으로 컴포넌트 하나 생성
const component = document.createElement(vNode.type);

// vNode에 있는 props로 component
updateAttributes(component, vNode.props);
// component의 자식들을 creatElement재귀를 돌려서 appendChild시키기
vNode.children.forEach((child) => {
component.appendChild(createElement(child));
});
// 최종 컴포넌트 리턴
return component;
}
/* create할 떄 attribute값을 업데이트 해주는 함수 */
function updateAttributes($el, props) {
// props 값이 존재해야만 할 수 있음
if (props) {
Object.entries(props).forEach(([key, value]) => {
// 이벤트 함수인 경우
if (key.startsWith("on") && typeof value === "function") {
const eventType = key.toLowerCase().slice(2); // on 글자 제외
addEvent($el, eventType, value); // 이벤트를 등록하기. 등록하고자 하는 컴포넌트, 이벤트 타입, 이벤트시 실행될 함수
} else if (key === "className") {
// 스타일 적용하기
$el.setAttribute("class", value);
} else {
// 나머지 id 값 등
$el.setAttribute(key, value);
}
});
}
}
1 change: 1 addition & 0 deletions src/lib/createObserver.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//
export const createObserver = () => {
const listeners = new Set();
const subscribe = (fn) => listeners.add(fn);
Expand Down
2 changes: 1 addition & 1 deletion src/lib/createStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const createStore = (initialState, initialActions) => {

let state = { ...initialState };

// 상태 추가함수
const setState = (newState) => {
state = { ...state, ...newState };
notify();
Expand All @@ -18,6 +19,5 @@ export const createStore = (initialState, initialActions) => {
(...args) => setState(value(getState(), ...args)),
]),
);

return { getState, setState, subscribe, actions };
};
13 changes: 12 additions & 1 deletion src/lib/createVNode.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
export function createVNode(type, props, ...children) {
return {};
// depth 없이 펼치기
const flattenChildren = (children) => {
return children.flat(Infinity).filter((child) => {
return child || child === 0 || child === "";
});
};

return {
type: type,
props: props,
children: flattenChildren(children),
};
}
203 changes: 200 additions & 3 deletions src/lib/eventManager.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,202 @@
export function setupEventListeners(root) {}
// 사용 중인 이벤트를 담을 객체
// const eventMap = new Map();
// let rootElement = null;
//
// // eventMap = {
// // element : {
// // eventType : handler
// // }
// // }
//
// // 컴포넌트에 이벤트를 추가해주는 함수
// export function addEvent(element, eventType, handler) {
// let thisElement = element;
// while (thisElement && thisElement !== rootElement) {
// if (eventMap.get(thisElement)) {
// eventMap.get(thisElement).set(eventType, handler);
// } else {
// const newEventMap = new Map();
// newEventMap.set(eventType, handler);
// eventMap.set(thisElement, newEventMap);
// }
// console.log("부모", thisElement.parentElement)
// thisElement = thisElement.parentElement;
// }
// }
//
// export function setupEventListeners(root) {
// rootElement = root;
// Object.entries(eventMap).forEach(([element, eventTypeMap]) => {
// Object.entries(eventTypeMap).forEach(([eventType, handler]) => {
// rootElement.removeEventListener(eventType, (event) => handleEvent(element, event));
// rootElement.addEventListener(eventType, (event) => handleEvent(element, event));
// })
// })
// }
//
// export function handleEvent(element, event) {
// let target = event.target;
// // 이벤트 버블 현상으로 Root까지 찾아나가기
// while (target && target !== rootElement) {
// const eventHandlerMap = eventMap.get(element);
// if (eventHandlerMap) {
// Object.entries(eventHandlerMap).forEach(([eventType, handler]) => {
// handler(event);
// });
// }
// target = target.parentElement;
// }
// }
//
// export function removeEvent(element, eventType, handler) {
// let thisElement = element;
// while (thisElement && thisElement !== rootElement) {
// const eventTypeMap = eventMap.get(thisElement);
// if (eventTypeMap) {
// if (eventTypeMap.get(eventType)) {
// eventTypeMap.delete(eventType)
// }
// if(Object.entries(eventTypeMap).length === 0) {
// eventMap.delete(thisElement);
// }
// } else {
// return;
// }
//
// if (thisElement.size === 0) {
// eventMap.delete(thisElement);
// const eventTypeMap = eventMap.get(thisElement);
// Object.entries(([key, value]) => {
// if (rootElement && rootElement._listener?.has(key)) {
// rootElement.removeEventListener(key, (event) => handleEvent(thisElement, event), true);
// rootElement._listener.delete(key);
// }
// })
//
// }
// thisElement = element.parentElement;
// }
// }

export function addEvent(element, eventType, handler) {}
const eventMap = new Map();
let rootElement = null;

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

export function setupEventListeners(root) {
// 기존 리스너 제거
if (rootElement) {
const handlers = rootElement._eventHandlers || new Map();
for (const [type, handler] of handlers) {
rootElement.removeEventListener(type, handler);
}
}

rootElement = root;
rootElement._eventHandlers = new Map();

for (const eventData of eventMap) {
const eventTypeMap = eventData[1];
for (const eventTypeData of eventTypeMap) {
const eventType = eventTypeData[0];
if (!rootElement._eventHandlers.has(eventType)) {
const boundHandler = (event) => handleEvent(event);
rootElement._eventHandlers.set(eventType, boundHandler);
rootElement.addEventListener(eventType, boundHandler);
}
}
}
}

export function handleEvent(event) {
let target = event.target;
const eventType = event.type;

while (target && target !== rootElement) {
if (eventMap.has(target)) {
const handler = eventMap.get(target).get(eventType);
if (handler) {
// console.log("여기 에러나나", handler, event)
handler?.(event);
}
}
target = target.parentElement;
}
}

export function removeEvent(element, eventType) {
if (!eventMap.has(element)) return;

const elementEvents = eventMap.get(element);
elementEvents.delete(eventType);

if (elementEvents.size === 0) {
eventMap.delete(element);
}

// 해당 이벤트 타입에 대한 리스너가 더 이상 필요없는 경우
if (![...eventMap.values()].some((map) => map.has(eventType))) {
const handler = rootElement._eventHandlers?.get(eventType);
if (handler) {
rootElement.removeEventListener(eventType, handler, true);
rootElement._eventHandlers.delete(eventType);
}
}
}

// function handleEvent(event) {
// let target = event.target;
// while (target && target !== rootElement) {
// const elementHandlers = eventMap.get(event.type)?.get(target);
// if (elementHandlers) {
// elementHandlers.forEach((handler) => handler(event));
// }
// target = target.parentNode;
// }
// }
//
// export function setupEventListeners(root) {
// rootElement = root;
// eventMap.forEach((handlers, eventType) => {
// rootElement.removeEventListener(eventType, handleEvent);
// rootElement.addEventListener(eventType, handleEvent);
// });
// }
//
// // 컴포넌트이 이벤트를 추가해주는 함수
// export function addEvent(element, eventType, handler) {
// if (!eventMap.has(eventType)) {
// eventMap.set(eventType, new WeakMap());
// }
// const elementMap = eventMap.get(eventType);
// if (!elementMap.has(element)) {
// elementMap.set(element, new Set());
// }
// elementMap.get(element).add(handler);
// }
//
// export function removeEvent(element, eventType, handler) {
// const elementMap = eventMap.get(eventType);
// if (!elementMap) return;
//
// const handlers = elementMap.get(element);
// if (handlers) {
// handlers.delete(handler);
// if (handlers.size === 0) {
// elementMap.delete(element);
// }
// }
//
// if (element.size === 0) {
// eventMap.delete(eventType);
// if (rootElement && rootElement._listener?.has(eventType)) {
// rootElement.removeEventListener(eventType, handleEvent, true);
// rootElement._listener.delete(eventType);
// }
// }
// }
27 changes: 26 additions & 1 deletion src/lib/normalizeVNode.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,28 @@
export function normalizeVNode(vNode) {
return vNode;
// 값이 없는 경우는 빈 스트링으로
if (vNode === null || vNode === undefined || typeof vNode === "boolean") {
return "";
}

// 스프링이거나 숫자인 경우는 string으로
if (typeof vNode === "string" || typeof vNode === "number") {
return String(vNode);
}

// 함수형으로 들어온다면. HomePage.jsx 같은 값은 함수이다. 함수인 경우는 type에 함수가 있음
// 함수를 실행하는데, 내부 값에는 props와 children을 전달한다.
if (typeof vNode.type === "function") {
// console.log("함수로 들어온다는게 뭐야?", vNode)
return normalizeVNode(
vNode.type({ ...vNode.props, children: vNode.children }),
);
}

// 일반 Object 타입인 경우
return {
...vNode,
children: vNode.children
?.map((child) => normalizeVNode(child))
.filter(Boolean),
};
}
Loading

0 comments on commit 5f3ac90

Please sign in to comment.