-
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
[11팀 박근백] [Chapter 1-2] 프레임워크 없이 SPA 만들기 #7
base: main
Are you sure you want to change the base?
Conversation
const child = container.querySelector("#child"); | ||
child.click(); | ||
expect(clickHandler1).toHaveBeenCalledTimes(0); | ||
}); |
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.
tc 가져가서 저도 버블링 구현 트라이 해봐도 될까요? ㅎㅎ
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.
우와... 테스트 코드도 짜셨군요!👍
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.
테스트 코드까지.. 정말 대단하십니다👍👍👍
<form | ||
className="mb-4 bg-white rounded-lg shadow p-4" | ||
onSubmit={handleAddPost} | ||
> |
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.
시맨틱 태그를 활용해서 가독성이나 seo를 챙기는 부분일까요?
spa관점으로 보면 form태그는 preventDefault를 해줘야하는 trade-off가 존재해서 의견이 궁금합니다.
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.
아 이부분을 이렇게 구현한 이유는 FormData를 사용하기 위해서 였습니다.
preventDefault
를 해주는것에 대한 단점이 혹시 무엇인지 여쭤봐도 될까요??
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.
장/단 이라기보다는, 시맨틱 태그의 기본적인 동작을 저지한다는 관점에서 html의 의도가 아닌 개발자의 의도를 입힌다고 생각됩니다.
여러 데이터를 폼으로 받지 않는 경우이니, 저는 form을 안썼을 것 같다고 생각이 들어서 의견을 여쭤봤어요 ㅎㅎ
코드에 문제가 있단 것은 절대 아닙니다!
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.
저는 react-hook-form와 zod 를 주로 활용했었는데요.
사용자에게 입력을 받고 서버로 요청을 보내는 로직에서는 form 태그를 주로 활용해서 자연스레 저렇게 짠것 같기도 합니다 !
src/lib/renderElement.js
Outdated
if (virtualDOM === null) { | ||
const element = createElement(normalizedVNode); | ||
container.append(element); | ||
virtualDOM = normalizedVNode; | ||
setupEventListeners(container); | ||
return; | ||
} | ||
|
||
if (container.firstChild) { | ||
updateElement(container, normalizedVNode, virtualDOM); | ||
virtualDOM = normalizedVNode; | ||
} else { | ||
const element = createElement(normalizedVNode); | ||
container.append(element); | ||
virtualDOM = normalizedVNode; | ||
} | ||
setupEventListeners(container); |
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.
- setupEventListeners
- oldVDOM 업데이트
두개의 동작은 어떠한 상황에도 element 생성/업데이트 이후에 동작하는 것으로 보입니다.
공통으로 사용되는 로직을 여러번 작성하는 부분을 개선할 수 있지 않을까 싶어요!
(사실 제가 해당코드를 디버깅 중에 참고했었습니다. 제 생각에 개선해서 짰다고 생각하는데 한번 봐주시고 같이 얘기해도 좋을 것 같아요 ㅎㅎ)
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.
이 코드를 이렇게 짠 이유는 준일 코치님께서 알려주셨던 https://jamie95.tistory.com/99 의 첫번째 항목을 고려해서 짠것이였는데요.
지금보니 불필요한 코드의 반복이 보여서 별로이긴 하네요..
준만님의 코드가 더 간결해보여서 좋긴한데 저 글의 첫번째 항목을 지키면서 불필요한 반복을 줄일 방법은 혹시 없을까요?
src/lib/createVNode.js
Outdated
return { | ||
type, | ||
props, | ||
children: children.flat(Infinity).filter(isRenderedVNode), |
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.
아하..저는 재귀 평탄화 함수를 직접짰는데,, 그냥 flat(무한뎁스)로 해결하면 되는거였군요 ㅋㅋ
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.
아하 그래서 재귀에대한 말씀을 하셨었군요.
src/lib/eventManager.js
Outdated
|
||
let currentElement = event.target; | ||
|
||
while (currentElement && !event.isPropatation()) { |
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.
오타발견 입니다.ㅎㅎ
isPropatation -> isPropagation
} | ||
|
||
const handlerCache = eventManager.get(eventType); | ||
handlerCache.delete(element); |
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.
key: element
value: handler
의 구조를 가진 WeakMap 객체로 타겟 엘리먼트에 대한 핸들러를 가지고 있는 용도입니다.
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.
덕분에 많이 알아갑니다,,,,bbb
const updateLikeUsers = (post) => { | ||
const isLiked = post.likeUsers.some( | ||
(user) => user.username === currentUser.username, | ||
); |
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.
저랑 로직이 거의 비슷한데 저는 includes 메서드를 썼습니다.
문득 some을 사용해서 헬퍼함수를 넣어 처리하는것과 단순히 includes를 이용하는것 중 어떤게 좋은지 궁금하네요 ㅎㅎ
제 생각에는 이런 케이스에서는 includes가 가독성 + 코드간소화 를 모두 챙길 수 있는 것 같습니다.
결론으로, 복잡한 헬퍼함수 들어갈때는 array메소드 조합보다는 헬퍼함수를 받는 some, reduce같은 종류의 함수들이 좋을 것 같네요.
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.
likeUsers에 들어있는 데이터의 타입이 다른게 가장 큰 이유일거 같습니다.
저는 likeUsers에 이름뿐만이 아닌 user 객체를 넣었거든요.
단순히 string 만을 넣었다면 includes가 더 좋은것 같긴 합니다!
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.
오 저는 객체를 넣으면 some
메서드를 사용하면 되는 군요!
저는 user
를 넣을까 하다가 객체를 넣으면 includes
가 제대로 동작하지 않을 것 같아서? username
넣도록 우회했는데..👍
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.
근백님 모난 실력이지만 열심히 리뷰해봤습니다.. 여러가지 방법론들을 고민하고 적용하려고 노력했던 흔적들이 잘 느껴져서 좋았습니다 ㅎㅎ 테스트 케이스를 직접 작성해보고 테스트해보신 것도 정말 대단하십니다 저는 로그만 주구장창 찍는데.. 다음부터 저도 시도해보겠습니다 ~!!
src/lib/util.js
Outdated
export const isRenderedVNode = (vNode) => | ||
vNode !== null && vNode !== undefined && typeof vNode !== "boolean"; | ||
|
||
export const isTextVNode = (vNode) => | ||
typeof vNode === "string" || typeof vNode === "number"; | ||
|
||
export const isEvent = (key) => key.startsWith("on"); | ||
|
||
export const isClassName = (key) => key === "className"; |
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.
자주 사용하는 함수에 대해 유틸 함수를 만들어준 부분 좋은 것 같습니다 ㅎㅎ 다만 util 네이밍 자체가 좀 범용적인 것 같아서 조금 더 구체화하면 좋을 것 같습니다
(당장 마땅히 생각나는 것 없지만.. NodeUtil? 한번 고민해보면 좋을 것 같습니다 .. )
src/lib/updateElement.js
Outdated
} | ||
|
||
function updateAttributes(target, originNewProps, originOldProps) { | ||
Object.keys(originOldProps ?? {}).forEach((key) => { |
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.
여기서 null값의 예외를 처리해주기보단 함수 시그니쳐나 호출 부분에서 처리해주는 건 어떨까요? 지금과 같이 수행하면 추후에 originOldProps를 하단에서 다시 사용하게 될 때 null값에 대한 예외를 다시 처리해주어야 할 것 같아요
function updateAttributes(target, originNewProps = {}, originOldProps = {}) {}
updateAttributes(
parentElement.childNodes[index],
newNode.props || {},
oldNode.props || {},
);
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.
�지금 당장에는 문제가 없지만 originOldProps를 하단에서 다시 사용하는 경우에 또 null check를 해야하는 경우가 생길 수 있다고 고려했습니다! 예를 들어 위와 같이 사용한 뒤에,
Object.keys(originNewProps ?? {}).forEach((key) => {
oldProp = OriginOldProps[index]; // OriginOldProps에 대한 null check 다시 해야 함
이런 경우가 생길 수 있어서 디버깅이나 오류 발생 가능성을 줄이기 위해 초기에 선언해주면 어떨까? 하는 측면에서 제안드렸습니다
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.
아 이해했습니다.
상세한 예시까지 알려주셔서 감사합니다 !
@@ -3,8 +3,21 @@ import { createElement } from "./createElement"; | |||
import { normalizeVNode } from "./normalizeVNode"; | |||
import { updateElement } from "./updateElement"; | |||
|
|||
let virtualDOM = null; |
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.
virtualDOM을 전역 객체로 사용했을 때 특별한 문제는 없었을까요? 저는 root container의 내부 변수로 할당해주었는데 container가 GC에 의해 없어질 때 같이 제거될 수 있기 때문이었어요. 그리고 slack에서 다른 분들 얘기 나눈 것들 보면 virtualDOM을 WeakMap 자료구조를 사용해서 위와 마찬가지로 GC에 의해 자동적으로 제거될 수 있게 하는 방법도 있더라구요 ㅎㅎ 다른 의도가 있는게 아니라면 이런 방법들도 고려해봐도 좋을 것 같아요!
src/lib/eventManager.js
Outdated
root?.removeEventListener(eventType, handleGlobalEvent); | ||
root?.addEventListener(eventType, handleGlobalEvent); |
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.
여기서 root에 옵셔널 체이닝을 사용한 이유가 있을까요? root가 없어도 이벤트 리스너 등록/삭제 실패가 프로그램 흐름에 문제는 없기 때문인가요?
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.
습관적으로 적었다가 지우지 않은것 같네요..
이런 곳을 볼때마다 정말 TS의 중요성을 다시금 느끼게 되는것 같아요.
src/lib/normalizeVNode.js
Outdated
if (!isRenderedVNode(vNode)) { | ||
return ""; | ||
} |
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.
만약 의도가 "렌더링이 가능한 가상 돔 노드가 아닌 경우"에 대한 거라면 isRenderableVNode라는 네이밍은 어떠신가요 ?!
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.
좋은것 같습니다 ! 영어의 부족함이 여기서 탄로나는 ....
src/lib/eventManager.js
Outdated
function createSyntheticEvent(event) { | ||
let propagationStopped = false; | ||
return { | ||
type: event.type, | ||
target: event.target, | ||
currentTarget: event.target, | ||
preventDefault() { | ||
event.preventDefault(); | ||
}, | ||
stopPropagation() { | ||
propagationStopped = true; | ||
event.stopPropagation(); | ||
}, | ||
isPropagationStopped() { | ||
return propagationStopped; | ||
}, | ||
nativeEvent: event, | ||
}; | ||
} |
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.
합성 이벤트를 활용하셨네요 !! 저는 target -> 실제로 이벤트가 발생한 가장 안쪽 요소, currentTarget -> 현재 이벤트 핸들러가 동작하고 있는 요소로 이해했습니다.
그런데 현재 코드에선 target과 currentTarget이 동일한 값을 참조하고 있는데 이벤트 버블링 과정에서 currentTarget을 업데이트해주면 좋을 것 같아요!!
if (handlers.has(currentElement)) {
syntheticEvent.currentTarget = currentElement;
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.
오 감사합니다 !
currentTarget을 사용을 안하고 있다보니 신경을 안썼네요.
src/lib/eventManager.js
Outdated
if (handlers.has(currentElement)) { | ||
const handler = handlers.get(currentElement); | ||
handler(event); | ||
} | ||
currentElement = currentElement.parentElement; |
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.
핸들러 함수가 존재하지 않거나 핸들러 함수 실행에 실패했을 때? 에 대한 예외처리도 있으면 좋을 것 같아요~!
if (handlers.has(currentElement)) { | |
const handler = handlers.get(currentElement); | |
handler(event); | |
} | |
currentElement = currentElement.parentElement; | |
if (handlers.has(currentElement)) { | |
const handler = handlers.get(currentElement); | |
try { | |
handler(event); | |
} catch (error) { | |
// 커스텀한 에러 객체 생성도 좋은 방법인 것 같습니다 | |
} | |
} | |
currentElement = currentElement.parentElement; |
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.
handler의 유무는 위에서 체크하고 있어 괜찮을것 같아보이긴 한데 혹시 함수실행에 실패했을때에는 어떠한 경우가 있을까요?
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.
handlers에 has로 currentElement 유무를 확인한 뒤 get을 해주어 handler를 가져오다보니 내부적으로 유효하지 않은 값이 할당되어있을 가능성을 고려해봤는데 잘못 생각한 것 같네요..ㅎ 다만 handlers.has → GC 수집 → handlers.get 이런 흐름도 예측해볼 수 있긴한데 너무 이론적인 엣지 케이스인 것 같아 그냥 무시하셔도 될 듯 합니다 ㅋㅋ..
함수 실행 실패는 handler 수행 동작 과정에서 어떤 오류가 발생해 실패한 경우 프로그램에 지장을 주지 않게 하기 위해 try catch로 적절하게 예외처리하면 좋지 않을까 하는 생각이었어요! 저도 구체적으로 떠오르진 않아 GPT한테 물어봤는데,
- 함수 파라미터 문제
- 함수 내부에서 참조하는 외부 값이 변경 또는 삭제
- this 컨텍스트 바인딩 문제
- 이밴트 객체 속성 접근 문제
뭐 이런 것들이 있을 수 있다고는 합니다..!
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.
역시 도운님 ,,, 예시 최고 bb
const child = container.querySelector("#child"); | ||
child.click(); | ||
expect(clickHandler1).toHaveBeenCalledTimes(0); | ||
}); |
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.
우와... 테스트 코드도 짜셨군요!👍
src/lib/eventManager.js
Outdated
function createSyntheticEvent(event) { | ||
let propagationStopped = false; | ||
return { | ||
type: event.type, | ||
target: event.target, | ||
currentTarget: event.target, | ||
preventDefault() { | ||
event.preventDefault(); | ||
}, | ||
stopPropagation() { | ||
propagationStopped = true; | ||
event.stopPropagation(); | ||
}, | ||
isPropagationStopped() { | ||
return propagationStopped; | ||
}, | ||
nativeEvent: event, | ||
}; | ||
} |
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.
우와.. stopPropagation
을 사용 유무를 체크하시려고 이벤트 객체를 커스텀해서 내보내는 건가요??
진짜 대단하�시네용....👍
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.
네 맞습니다!
src/lib/eventManager.js
Outdated
function handleGlobalEvent(event) { | ||
event = createSyntheticEvent(event); | ||
const eventType = event.type; | ||
const handlers = eventManager.get(eventType); | ||
|
||
let currentElement = event.target; | ||
while (currentElement && !event.isPropagationStopped()) { | ||
if (handlers.has(currentElement)) { | ||
const handler = handlers.get(currentElement); | ||
handler(event); | ||
} | ||
currentElement = currentElement.parentElement; | ||
} | ||
} |
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.
이벤트 버블링 구현까지...👍
const updateLikeUsers = (post) => { | ||
const isLiked = post.likeUsers.some( | ||
(user) => user.username === currentUser.username, | ||
); |
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.
오 저는 객체를 넣으면 some
메서드를 사용하면 되는 군요!
저는 user
를 넣을까 하다가 객체를 넣으면 includes
가 제대로 동작하지 않을 것 같아서? username
넣도록 우회했는데..👍
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.
안녕하세요 근백님 ~! 담당 학습메이트 오소현입니다 :)
요번주 휴일없이,,,⭐️ 크리스마스에도 달려주셨던 근백님 정말 고생많으셨습니다 :0 최고최고!! 다른 분들 코드 리뷰도 너무 적극적으로 해주시고, 이슈가 공유되면 바로 달려와 도움 주시는 근백님 덕분에 든든합니다!! 이번주도 과제 엄청 잘해주셨네요,, 특히 도운-준만-원정님께서 정말 꼼꼼하게 리뷰를 남겨주셔서 저도 리뷰 남겨주신 내용들 보면서 근백님의 코드가 인기쟁이인 이유를 알았습니다.. 굿굿 역시 최고최고 이번주 저랑 거의 zep에 같이 계셨는데 진짜 고생많으셨습니다 !! ❤️
또한 PR에도 이벤트 위임과 버블링을 가지고 이벤트 관리 방법엄청 잘 설명해주시고, 이를 통해 리스너의 수를 최소화하는 것도 잘 정리해주셔서 이해하기 쉬웠습니다! 잘 정리해주셔서 너무 감사해요 :) 이번주 내내 fiber로 구현하시는 걸 도전하셨는데 근백님은 반드시 !! 해내실 것이라고 ㅎㅎ 생각합니다 ^0^
부족한 실력이지만 요번주도 한번 리뷰를 남겨보았어요! 다른 분들 리뷰 내용에 공감가는 것도 많고 bb 최고였습니다 근백님께서 요번주 과제를 한번더 되돌아보는 계기가 되었으면 좋겠어요🍀🍀
이번주 과제도 너무 수고많으셨고, 앞으로의 과제도 화이팅입니다! 근백님의항상 앞으로가 기대됩니다 ㅎㅎㅎ 이번주 고생하셨어요 :)
src/stores/globalStore.js
Outdated
addPost(state, content) { | ||
const { currentUser } = state; | ||
|
||
const newPost = { | ||
id: state.posts.length + 1, | ||
author: currentUser.username, |
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.
state.posts.length + 1 이렇게 구현하면 나중에 삭제 추가되면 포스트 추가 시 ID가 중복될 것 같네용
addPost(state, content) { | |
const { currentUser } = state; | |
const newPost = { | |
id: state.posts.length + 1, | |
author: currentUser.username, | |
addPost(state, content) { | |
const newPostId = state.posts.reduce((maxId, post) => Math.max(post.id, maxId), 0) + 1; | |
const newPost = { | |
id: newPostId, | |
author: state.currentUser.username, |
요렇게 바꿔보면 어떨까 싶습니다!
src/lib/util.js
Outdated
export const isTextVNode = (vNode) => | ||
typeof vNode === "string" || typeof vNode === "number"; | ||
|
||
export const isEvent = (key) => key.startsWith("on"); | ||
|
||
export const isClassName = (key) => key === "className"; |
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.
제가 도운님 리뷰에 이어서 한번 추천해보겠습니다 ㅎㅎ
isTextNodeVNode
, 주어진 키가 "className"인지 알아보니까 isClassNameProp
? 어떠신가요 !
src/lib/eventManager.js
Outdated
return { | ||
type: event.type, | ||
target: event.target, | ||
currentTarget: event.target, |
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.
약간 여기서 currentTarget이 항상 event.target과 같게 설정되는것 같은데 이벤트 핸들러가 호출될 때 currentTarget이 설정되어야 하니까 요 부분을 handleGlobalEvent로 옮기는 건 어떤가요??
src/lib/eventManager.js
Outdated
if (handlers.has(currentElement)) { | ||
const handler = handlers.get(currentElement); | ||
handler(event); | ||
} | ||
currentElement = currentElement.parentElement; |
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.
역시 도운님 ,,, 예시 최고 bb
} | ||
|
||
const handlerCache = eventManager.get(eventType); | ||
handlerCache.delete(element); |
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.
덕분에 많이 알아갑니다,,,,bbb
src/lib/updateElement.js
Outdated
export function updateElement(parentElement, newNode, oldNode, index = 0) { | ||
// 새로운 노드가 추가될 경우 | ||
if (newNode && !oldNode) { | ||
parentElement.appendChild(createElement(newNode)); | ||
return; | ||
} | ||
|
||
// 이전 노드가 삭제된 경우 | ||
if (!newNode && oldNode) { | ||
parentElement.removeChild(parentElement.childNodes[index]); | ||
return; | ||
} | ||
|
||
// 노드의 타입이 다른경우 | ||
if (newNode.type !== oldNode.type) { | ||
parentElement.replaceChild( | ||
createElement(newNode), | ||
parentElement.childNodes[index], | ||
); | ||
return; | ||
} | ||
|
||
// 텍스트 노드일 경우 | ||
if ( | ||
typeof newNode === "string" && | ||
typeof oldNode === "string" && | ||
newNode !== oldNode | ||
) { | ||
parentElement.replaceChild( | ||
createElement(newNode), | ||
parentElement.childNodes[index], | ||
); | ||
return; | ||
} | ||
|
||
if (isChangedAttributes(newNode.props, oldNode.props)) { | ||
updateAttributes( | ||
parentElement.childNodes[index], | ||
newNode.props, | ||
oldNode.props, | ||
); | ||
} | ||
|
||
const newNodeChildren = newNode.children ?? []; | ||
const oldNodeChildren = oldNode.children ?? []; | ||
const maxChildren = Math.max(newNodeChildren.length, oldNodeChildren.length); | ||
|
||
for (let i = 0; i < maxChildren; i++) { | ||
updateElement( | ||
parentElement.childNodes[index], | ||
newNodeChildren[i], | ||
oldNodeChildren[i], | ||
i, | ||
); | ||
} | ||
} |
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.
이 함수 다 좋네요 bb 그런데 중복되는 로직이 있어보이고, 텍스트 노드 업데이트 로직을 textContent 변경으로 구현해보면 더 좋을것 같아서 코드를 제안해봅니다~!
export function updateElement(parentElement, newNode, oldNode, index = 0) { | |
// 새로운 노드가 추가될 경우 | |
if (newNode && !oldNode) { | |
parentElement.appendChild(createElement(newNode)); | |
return; | |
} | |
// 이전 노드가 삭제된 경우 | |
if (!newNode && oldNode) { | |
parentElement.removeChild(parentElement.childNodes[index]); | |
return; | |
} | |
// 노드의 타입이 다른경우 | |
if (newNode.type !== oldNode.type) { | |
parentElement.replaceChild( | |
createElement(newNode), | |
parentElement.childNodes[index], | |
); | |
return; | |
} | |
// 텍스트 노드일 경우 | |
if ( | |
typeof newNode === "string" && | |
typeof oldNode === "string" && | |
newNode !== oldNode | |
) { | |
parentElement.replaceChild( | |
createElement(newNode), | |
parentElement.childNodes[index], | |
); | |
return; | |
} | |
if (isChangedAttributes(newNode.props, oldNode.props)) { | |
updateAttributes( | |
parentElement.childNodes[index], | |
newNode.props, | |
oldNode.props, | |
); | |
} | |
const newNodeChildren = newNode.children ?? []; | |
const oldNodeChildren = oldNode.children ?? []; | |
const maxChildren = Math.max(newNodeChildren.length, oldNodeChildren.length); | |
for (let i = 0; i < maxChildren; i++) { | |
updateElement( | |
parentElement.childNodes[index], | |
newNodeChildren[i], | |
oldNodeChildren[i], | |
i, | |
); | |
} | |
} | |
export function updateElement(parentElement, newNode, oldNode, index = 0) { | |
if (!oldNode && newNode) { | |
parentElement.appendChild(createElement(newNode)); | |
return; | |
} | |
if (!newNode) { | |
parentElement.removeChild(parentElement.childNodes[index]); | |
return; | |
} | |
const childElement = parentElement.childNodes[index]; | |
if (newNode.type !== oldNode.type) { | |
parentElement.replaceChild(createElement(newNode), childElement); | |
return; | |
} | |
if (typeof newNode === "string" && newNode !== oldNode) { | |
childElement.textContent = newNode; | |
} | |
if (isChangedAttributes(newNode.props, oldNode.props)) { | |
updateAttributes(childElement, newNode.props, oldNode.props); | |
} | |
const maxLength = Math.max(newNode.children?.length || 0, oldNode.children?.length || 0); | |
for (let i = 0; i < maxLength; i++) { | |
updateElement(childElement, newNode.children[i], oldNode.children[i], i); | |
} | |
} |
const child = container.querySelector("#child"); | ||
child.click(); | ||
expect(clickHandler1).toHaveBeenCalledTimes(0); | ||
}); |
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.
테스트 코드까지.. 정말 대단하십니다👍👍👍
const handleToggleLike = () => { | ||
if (!loggedIn) { | ||
alert("로그인 후 이용해주세요"); | ||
return; | ||
} | ||
toggleLike(id); | ||
}; | ||
|
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.
와 toggleLike
과 관련없는 loggedIn
상태를 외부에서 처리하는게 훨씬 가독성이 좋고 재사용성이 좋아지네요👍👍
if (isClassNameProps(key)) { | ||
$el.setAttribute("class", props[key]); | ||
return; | ||
} |
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.
유효성체크용 util 함수를 정말 잘 활용하셨네요!!! 저도 놓친부분이 많아서 반성하고 갑니다,,
과제 체크포인트
기본과제
가상돔을 기반으로 렌더링하기
이벤트 위임
심화 과제
1) Diff 알고리즘 구현
2) 포스트 추가/좋아요 기능 구현
과제 셀프회고
기술적 성장
코드 품질
학습 효과 분석
과제 피드백
리뷰 받고 싶은 내용
작성중