-
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
[12팀 배성규] [Chapter 1-2] 프레임워크 없이 SPA 만들기 #34
Open
pangkyu
wants to merge
14
commits into
hanghae-plus:main
Choose a base branch
from
pangkyu:main
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
43ff5e3
Feat : vnode 평탄화
pangkyu 29bfca8
Update createVNode.js
pangkyu 316c6ca
Merge pull request #1 from pangkyu/feat/createVNode
pangkyu 9d123b3
Refactor : 변수 없이 바로 리턴하도록 수정
pangkyu a8ca97d
Feat : 정규화 기능 추가
pangkyu 445dc94
Feat : 컴포넌트 정규화 & falsy 필터
pangkyu e987a5e
Merge pull request #2 from pangkyu/feat/normalizeVNode
pangkyu 829e002
Feat : create elements
pangkyu b1fb46d
Merge pull request #3 from pangkyu/feat/createElement
pangkyu 3c18801
Feat : 이벤트매니저
pangkyu 41688e3
Merge pull request #4 from pangkyu/feat/eventManager
pangkyu 83fd282
Feat : 여러 이벤트 처리하도록 변경
pangkyu 20a11c9
Feat : render elements
pangkyu 99e7430
Merge pull request #5 from pangkyu/feat/renderElement
pangkyu File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export const eventTypes = ["click", "focus", "mouseover", "keydown"]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,49 @@ | ||
import { addEvent } from "./eventManager"; | ||
// import { addEvent } from "./eventManager"; | ||
|
||
export function createElement(vNode) {} | ||
export function createElement(vNode) { | ||
if (vNode == null || typeof vNode === "boolean" || vNode === undefined) { | ||
return document.createTextNode(""); | ||
} | ||
|
||
function updateAttributes($el, props) {} | ||
if (typeof vNode === "string" || typeof vNode === "number") { | ||
return document.createTextNode(vNode); | ||
} | ||
|
||
if (Array.isArray(vNode)) { | ||
const fragement = document.createDocumentFragment(); | ||
|
||
vNode.forEach((child) => { | ||
const childNode = createElement(child); | ||
fragement.appendChild(childNode); | ||
}); | ||
return fragement; | ||
} | ||
|
||
const $el = document.createElement(vNode.type); | ||
updateAttributes($el, vNode.props); | ||
if (vNode.children) { | ||
vNode.children.forEach((child) => { | ||
const childNode = createElement(child); | ||
$el.appendChild(childNode); | ||
}); | ||
} else { | ||
$el.appendChild(createElement(vNode.children)); | ||
} | ||
|
||
return $el; | ||
} | ||
|
||
function updateAttributes($el, props) { | ||
if (!props) return; | ||
|
||
Object.entries(props).forEach(([key, value]) => { | ||
if (key === "className") { | ||
$el.setAttribute("class", value); | ||
} else if (key.startsWith("on")) { | ||
const eventType = key.slice(2).toLowerCase(); // 예: onClick -> click | ||
$el.addEventListener(eventType, value); // 이벤트 핸들러 등록 | ||
} else { | ||
$el.setAttribute(key, value); // 일반 속성 처리 | ||
} | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,7 @@ | ||
export function createVNode(type, props, ...children) { | ||
return {}; | ||
export function createVNode(type, props = {}, ...children) { | ||
return { | ||
type, | ||
props, | ||
children: children.flat(Infinity).filter((child) => child || child === 0), | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,57 @@ | ||
export function setupEventListeners(root) {} | ||
import { eventTypes } from "../constants/constant"; | ||
|
||
export function addEvent(element, eventType, handler) {} | ||
const eventMap = new Map(); | ||
|
||
export function removeEvent(element, eventType, handler) {} | ||
export function setupEventListeners(root) { | ||
eventTypes.forEach((eventType) => { | ||
root.addEventListener(eventType, (event) => { | ||
const target = event.target; | ||
|
||
for (const [element, handlers] of eventMap.entries()) { | ||
if (element === target || element.contains(target)) { | ||
const eventTypeHandlers = handlers.get("click"); | ||
if (eventTypeHandlers) { | ||
eventTypeHandlers.forEach((handler) => handler(event)); | ||
} | ||
} | ||
} | ||
}); | ||
}); | ||
} | ||
|
||
export function addEvent(element, eventType, handler) { | ||
if (!eventMap.has(element)) { | ||
eventMap.set(element, new Map()); | ||
} | ||
|
||
const handlers = eventMap.get(element); | ||
if (!handlers.has(eventType)) { | ||
handlers.set(eventType, []); | ||
} | ||
|
||
const handlerList = handlers.get(eventType); | ||
if (!handlerList.includes(handler)) { | ||
handlerList.push(handler); | ||
} | ||
} | ||
|
||
export function removeEvent(element, eventType, handler) { | ||
if (!eventMap.has(element)) return; | ||
|
||
const handlers = eventMap.get(element); | ||
if (handlers.has(eventType)) { | ||
const handlerList = handlers.get(eventType); | ||
const index = handlerList.indexOf(handler); | ||
|
||
if (index !== -1) { | ||
handlerList[index] = null; | ||
handlerList.splice(index, 1); | ||
} | ||
if (handlerList.length === 0) { | ||
handlers.delete(eventType); | ||
if (handlers.size === 0) { | ||
eventMap.delete(element); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,21 @@ | ||
export function normalizeVNode(vNode) { | ||
return vNode; | ||
if (typeof vNode === "number" || typeof vNode === "string") { | ||
return String(vNode); | ||
} | ||
if (vNode === null || vNode === undefined || typeof vNode === "boolean") { | ||
return ""; | ||
} | ||
|
||
if (typeof vNode.type === "function") { | ||
return normalizeVNode( | ||
vNode.type({ ...vNode.props, children: vNode.children }), | ||
); | ||
} | ||
return { | ||
...vNode, | ||
children: vNode.children | ||
.filter((child) => child || child === 0) | ||
.map((child) => normalizeVNode(child)) | ||
.filter((child) => child !== ""), | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,89 @@ | ||
import { addEvent, removeEvent } from "./eventManager"; | ||
import { createElement } from "./createElement.js"; | ||
|
||
function updateAttributes(target, originNewProps, originOldProps) {} | ||
function updateAttributes(target, originNewProps = {}, originOldProps = {}) { | ||
if (originOldProps) { | ||
Object.keys(originOldProps).forEach((key) => { | ||
if (key.startsWith("on")) { | ||
const eventType = key.slice(2).toLowerCase(); | ||
const oldHandler = originOldProps[key]; | ||
|
||
export function updateElement(parentElement, newNode, oldNode, index = 0) {} | ||
if (typeof oldHandler === "function") { | ||
removeEvent(target, eventType, oldHandler); | ||
originOldProps[key] = null; | ||
} | ||
} else if (!(key in originNewProps)) { | ||
target.removeAttribute(key); | ||
} | ||
}); | ||
} | ||
|
||
if (originNewProps) { | ||
Object.entries(originNewProps).forEach(([key, value]) => { | ||
const oldValue = originOldProps[key]; | ||
if (key === "className") { | ||
if (value !== oldValue) { | ||
target.className = value; | ||
} | ||
} else if (key.startsWith("on")) { | ||
const eventType = key.slice(2).toLowerCase(); | ||
if (value !== oldValue) { | ||
if (typeof oldValue === "function") { | ||
removeEvent(target, eventType, oldValue); | ||
} | ||
if (value) { | ||
addEvent(target, eventType, value); | ||
} else { | ||
target.removeEventListener(eventType, oldValue); | ||
} | ||
} | ||
} else if (key === "style" && typeof value === "object") { | ||
Object.assign(target.style, value); | ||
} else if (value !== oldValue) { | ||
target.setAttribute(key, value); | ||
} | ||
}); | ||
} | ||
} | ||
|
||
export function updateElement(parentElement, newNode, oldNode, index = 0) { | ||
if (!newNode && oldNode) { | ||
parentElement.removeChild(parentElement.childNodes[index]); | ||
return; | ||
} | ||
|
||
if (newNode && !oldNode) { | ||
const newElement = createElement(newNode); | ||
parentElement.append(newElement); | ||
return; | ||
} | ||
|
||
if (typeof newNode === "string" && typeof oldNode === "string") { | ||
if (newNode != oldNode) { | ||
parentElement.childNodes[index].textContent = newNode; | ||
} | ||
return; | ||
} | ||
|
||
if (newNode.type !== oldNode.type) { | ||
const newElement = createElement(newNode); | ||
const oldElement = parentElement.childNodes[index]; | ||
if (oldElement) { | ||
parentElement.replaceChild(newElement, parentElement.childNodes[index]); | ||
return; | ||
} | ||
parentElement.appendChild(newElement); | ||
return; | ||
} | ||
|
||
const element = parentElement.childNodes[index]; | ||
const newChildren = newNode.children; | ||
const oldChildren = oldNode.children; | ||
const maxLength = Math.max(newChildren.length, oldChildren.length); | ||
|
||
updateAttributes(element, newNode.props, oldNode.props); | ||
|
||
for (let i = 0; i < maxLength; i++) { | ||
updateElement(element, newChildren[i], oldChildren[i], i); | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
성규님~! 혹시 이벤트 핸들러 등록하는부분을 eventManager 에서 만드신 addEvent함수를 이용하는것에대해서 어떻게생각하시나요??
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.
함수를 제가 만들어놓고도 쓰지를 않고 있었네요,, ㅋㅋ 피드백 감사합니다 !
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.
안녕하세요 성규님-! 김나영 코치입니다.
리뷰해주신 분의 말씀대로 해당 부분은 매니저에서 만들어둔 addEvent를 사용하셔야 현재 fail나는 테스트코드가 해결될 것으로 보입니다. 아마 addEvent에서 Map자료구조에 이벤트가 등록이 되어있다는 가정하에 removeEvent가 동작했을텐데, 직접 요소에서 이벤트가 관리되고 있어 removeEvent로는 제거처리가 되지 않은듯합니다 :)
수정하시면 다른 TC가 아마 깨지실텐데요, 하나씩 풀어나가시면서 리팩토링을 해보시면 좋을 듯 합니다.
고생하셨습니다 :) @pangkyu