-
Notifications
You must be signed in to change notification settings - Fork 65
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
[13팀 김유진] [Chapter 1-2] 프레임워크 없이 SPA 만들기 #28
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,45 @@ | ||
import { addEvent } from "./eventManager"; | ||
|
||
export function createElement(vNode) {} | ||
function updateAttributes($el, props) { | ||
Object.entries(props).forEach(([attr, value]) => { | ||
if (attr.startsWith("on") && typeof value === "function") { | ||
addEvent($el, attr.slice(2).toLowerCase(), value); | ||
} else if (attr === "className") { | ||
$el.setAttribute("class", value); | ||
} else { | ||
$el.setAttribute(attr, value); | ||
} | ||
}); | ||
} | ||
|
||
function updateAttributes($el, props) {} | ||
export function createElement(vNode) { | ||
if (vNode === null || vNode === undefined || typeof vNode === "boolean") | ||
return document.createTextNode(""); | ||
|
||
if (typeof vNode === "string" || typeof vNode === "number") { | ||
return document.createTextNode(vNode); | ||
} | ||
|
||
if (Array.isArray(vNode)) { | ||
console.log("vNode Array", vNode); | ||
const fragment = document.createDocumentFragment(); | ||
vNode.forEach((child) => { | ||
if (child) { | ||
fragment.appendChild(createElement(child)); | ||
} | ||
}); | ||
return fragment; | ||
} | ||
|
||
const $el = document.createElement(vNode.type); | ||
|
||
if (vNode.props) { | ||
updateAttributes($el, vNode.props); | ||
} | ||
|
||
if (vNode.children) { | ||
$el.append(...vNode.children.map(createElement)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저는 appendChild만 알고 있었어서 append는 모지..?! 하고 찾아보니 용도는 비슷하지만
요런 차이점이 있더라구요! append가 더 최신 문법이고 유용한 것 같아용 하나 배워갑니다~~✨ |
||
} | ||
|
||
return $el; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,28 @@ | ||
export function createVNode(type, props, ...children) { | ||
return {}; | ||
// props.ref로 이벤트 등록 | ||
if (props?.ref && typeof props.ref === "function") { | ||
setTimeout(() => { | ||
const element = document.querySelector(`[id="${props.id}"]`); | ||
if (element) { | ||
// 이전에 등록된 클린업 함수가 있다면 실행 | ||
if (element._cleanup) { | ||
element._cleanup(); | ||
} | ||
|
||
// 새로운 ref 콜백 실행 및 반환된 클린업 함수 저장 | ||
const cleanup = props.ref(element.parentElement); | ||
if (typeof cleanup === "function") { | ||
element._cleanup = cleanup; | ||
} | ||
} | ||
}, 0); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
저는 처음에 setTimeout이 0초로 설정되어있어서 이러면 타임아웃을 거는 이유가 뭘까 생각해보고 찾아봤는데, 0으로 설정해도 바로 실행되는게 아니라는 것을 배우고 갑니다 ㅎ dom이 다 그려진 이후에 클린업을 체크하려고 넣으신걸로 이해했는데 제가 맞게 이해한게 맞을까요?? |
||
} | ||
|
||
return { | ||
type, | ||
props, | ||
children: children | ||
.flat(Infinity) | ||
.filter((value) => value === 0 || Boolean(value)), | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,53 @@ | ||
export function setupEventListeners(root) {} | ||
const eventMap = new Map(); | ||
let rootElement = null; | ||
|
||
export function addEvent(element, eventType, handler) {} | ||
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.parentElement; | ||
} | ||
} | ||
|
||
export function removeEvent(element, eventType, handler) {} | ||
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 (eventMap.size === 0) { | ||
eventMap.delete(eventType); | ||
if (rootElement && rootElement._listeners?.has(eventType)) { | ||
rootElement.removeEventListener(eventType, handleEvent, true); | ||
rootElement._listeners.delete(eventType); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,24 @@ | ||
export function normalizeVNode(vNode) { | ||
return vNode; | ||
if ( | ||
typeof vNode === "undefined" || | ||
vNode === null || | ||
typeof vNode === "boolean" | ||
) { | ||
return ""; | ||
} | ||
|
||
if (typeof vNode === "number" || typeof vNode === "string") { | ||
return String(vNode); | ||
} | ||
|
||
if (typeof vNode.type === "function") { | ||
return normalizeVNode( | ||
vNode.type({ ...vNode.props, children: vNode.children }), | ||
); | ||
} | ||
|
||
return { | ||
...vNode, | ||
children: vNode.children.map(normalizeVNode).filter(Boolean), | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,92 @@ | ||
import { addEvent, removeEvent } from "./eventManager"; | ||
import { createElement } from "./createElement.js"; | ||
|
||
function updateAttributes(target, originNewProps, originOldProps) {} | ||
function updateAttributes(target, originNewProps, originOldProps) { | ||
const newProps = originNewProps || {}; | ||
const oldProps = originOldProps || {}; | ||
|
||
export function updateElement(parentElement, newNode, oldNode, index = 0) {} | ||
for (const attr in oldProps) { | ||
if (!(attr in newProps)) { | ||
if (attr.startsWith("on") && typeof newProps[attr] !== "function") { | ||
removeEvent(target, attr.slice(2).toLowerCase(), oldProps[attr]); | ||
} else { | ||
target.removeAttribute(attr); | ||
} | ||
} | ||
} | ||
|
||
for (const attr in newProps) { | ||
if (oldProps[attr] !== newProps[attr]) { | ||
if (attr === "className") { | ||
target.className = newProps[attr]; | ||
} else if ( | ||
attr.startsWith("on") && | ||
typeof newProps[attr] === "function" | ||
) { | ||
const eventName = attr.slice(2).toLowerCase(); | ||
if (typeof oldProps[attr] === "function") { | ||
removeEvent(target, eventName, oldProps[attr]); | ||
} | ||
addEvent(target, eventName, newProps[attr]); | ||
} else if (attr === "style" && typeof newProps[attr] === "object") { | ||
Object.entries(newProps[attr]).forEach(([key, value]) => { | ||
target.style[key] = value; | ||
}); | ||
} else { | ||
target.setAttribute(attr, newProps[attr]); | ||
} | ||
} | ||
} | ||
} | ||
|
||
export function updateElement(parentElement, newNode, oldNode, index = 0) { | ||
if (!newNode && oldNode) { | ||
parentElement.removeChild(parentElement.childNodes[index]); | ||
return; | ||
} | ||
|
||
if (!parentElement.childNodes[index]) { | ||
parentElement.appendChild(createElement(newNode)); | ||
return; | ||
} | ||
|
||
if (!oldNode && newNode) { | ||
parentElement.appendChild(createElement(newNode)); | ||
return; | ||
} | ||
|
||
if (typeof newNode === "string" || typeof newNode === "number") { | ||
if (newNode !== oldNode) { | ||
const newTextNode = document.createTextNode(String(newNode)); | ||
parentElement.replaceChild(newTextNode, parentElement.childNodes[index]); | ||
} | ||
return; | ||
} | ||
|
||
if (newNode.type !== oldNode.type) { | ||
parentElement.replaceChild( | ||
createElement(newNode), | ||
parentElement.childNodes[index], | ||
); | ||
return; | ||
} | ||
|
||
updateAttributes( | ||
parentElement.childNodes[index], | ||
newNode.props, | ||
oldNode.props, | ||
); | ||
|
||
const newChildren = newNode.children || []; | ||
const oldChildren = oldNode.children || []; | ||
const maxLength = Math.max(newChildren.length, oldChildren.length); | ||
|
||
for (let i = 0; i < maxLength; i++) { | ||
updateElement( | ||
parentElement.childNodes[index], | ||
newChildren[i], | ||
oldChildren[i], | ||
i, | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export * from "./globalStore"; | ||
export * from "./postStore"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
콘솔로그가 귀엽게 남아있어용ㅎㅎ