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

[8팀 최준만] [Chapter 1-2] 프레임워크 없이 SPA 만들기 #2

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
37ac031
Create README.md
junman95 Dec 21, 2024
46c2252
feat: createVNode 함수 구현
junman95 Dec 23, 2024
ba6557b
feat: normalize 구현
junman95 Dec 23, 2024
17df315
fix: 종단에서 props 강제 합성하는 코드가 아닌 props 스프레드를 통한 전달로 재 구현
junman95 Dec 23, 2024
e398fa5
feat: createElement 구현
junman95 Dec 23, 2024
1c6fc3a
feat: eventManager 구현
junman95 Dec 24, 2024
51a666e
feat: renderElement 구현
junman95 Dec 24, 2024
0ff32e7
docs:-----------------기본 및 1-1 과제 통과-----------------------
junman95 Dec 24, 2024
245951e
feat: diff로직 구현
junman95 Dec 25, 2024
05b5ecc
feat: className-> class 변환함수 외부로 분리
junman95 Dec 25, 2024
e209f2b
feat: 속성 업데이트 구현
junman95 Dec 25, 2024
96c087f
fix: 이벤트 리스너 remove시 참조 주소 상이하여 제거 안되던 이슈 해결
junman95 Dec 25, 2024
2e255fc
fix: attr 할당 함수 재 작성을 통한 이슈 해결(원인 파악 필요)
junman95 Dec 25, 2024
e26bbb5
fix: 생성 로직 순서 개선 및 초기화 코드 제거를 통해 1-1 이슈 해결(원인 파악 필요)
junman95 Dec 25, 2024
0a3d138
feat: 코드 개선
junman95 Dec 25, 2024
11e904a
fix: 텍스트 노드 업데이트 시 parentElement의 textContent에 바로 삽입하게 되면 순서 보장이안되고 덮…
junman95 Dec 25, 2024
c7c7541
feat: 포스트 기능 구현
junman95 Dec 25, 2024
e5ad353
fix: 이벤트 할당 후 리턴해주지 않아 on~ 형태의 속성 할당되는 이슈 해결
junman95 Dec 25, 2024
1765bc8
fix: 미로그인 시 alert 후 함수 종료 토록 수정
junman95 Dec 25, 2024
3db4eae
docs: readme update
junman95 Dec 26, 2024
70f9469
fix: 속성으로 처리하여 로그아웃 후에도 좋아요의 색상이 여전히 파란색인 이슈 username 포함 여부 확인 로직으로 변경
junman95 Dec 27, 2024
d38022a
refactor: 코드 개선
junman95 Dec 27, 2024
20ff127
feat: postform 내용물 없을때도 정상 동작하도록 개선
junman95 Dec 27, 2024
a1339b7
feat: 이벤트 버블링 구현
junman95 Dec 27, 2024
f5f40fc
refactor: 코드 정리
junman95 Dec 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
480 changes: 480 additions & 0 deletions README.md

Large diffs are not rendered by default.

26 changes: 26 additions & 0 deletions src/__tests__/chapter1-2/custom.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* 개인적으로 만든 순수함수들에 대한 테스트 입니다.
*/

import { recursiveFlatten } from "../../utils/arrayUtils";

describe("유틸 함수 체크", () => {
describe("recursiveFlatten", () => {
it("올바른 구조의 평탄화된 배열을 생성해야 한다", () => {
const vNode = recursiveFlatten([
"Hello",
["world", ["i", "am", ["junman"]], "!"],
]);
expect(vNode).toEqual(["Hello", "world", "i", "am", "junman", "!"]);
});

it("falsy한 값은 포함되지 않아야 한다.", () => {
const vNode = recursiveFlatten(
["Hello", [null, [undefined], "world", "!"]],
(value) => Boolean(value),
);

expect(vNode).toEqual(["Hello", "world", "!"]);
});
});
});
junman95 marked this conversation as resolved.
Show resolved Hide resolved
11 changes: 10 additions & 1 deletion src/components/posts/Post.jsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
/** @jsx createVNode */
import { createVNode } from "../../lib";
import { addEvent, createVNode } from "../../lib";
import { globalStore } from "../../stores/globalStore.js";
import { toTimeFormat } from "../../utils/index.js";

export const Post = ({
id,
author,
time,
content,
likeUsers,
activationLike = false,
}) => {
const { like } = globalStore.actions;

const onClickLike = () => {
like(id);
};

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

export const PostForm = () => {
const { currentUser, loggedIn, posts } = globalStore.getState();

if (!loggedIn) {
return null;
}

const onClickAddPost = () => {
globalStore.setState({
posts: [
{
id: posts[0].id + 1,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

지금은 posts가 기본 더미 데이터로 설정되어있긴한데 posts가 비어있다면? 이라는 생각이 들었어요 (저도 사실 고민하다가 Date.now()로 넣어놓고 따로 수정은 안했네요.. ㅎㅎ 더 좋은 방법 있는지 고민해봐도 좋을 것 같습니다)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

id: posts ? posts.length + 1 : 1,

요래 바꿔봤는데 어떤가요? @devJayve

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저는 store쪽 보시면 id값을 내림차순으로 변경해 놨습니다.
가장 최근에 들어온게 가장 아이디가 크도록요!

저희 과제에선 삭제가 없어서 이렇게 처리했습니다 ㅋㅋ

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

20ff127

tc와 함께 구현해봤습니다.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

굿 저도 비슷한 생각을 가지고 있는데요!
저는 그래서 위의 코드에서 posts가 비어있을 때 에러가 발생할 수 있을때를 고려한 새 게시물의 id를 추가하는 id를 다음과 같이 추가로 구현해보면 어떨까 싶어요! 제가 지금 코드를 보고 생각한 코드는 다음과 같은데, 추가로 더 고려해보시면 좋을 것 같습니다!

Suggested change
const onClickAddPost = () => {
globalStore.setState({
posts: [
{
id: posts[0].id + 1,
const onClickAddPost = () => {
const newId = posts.length > 0 ? Math.max(...posts.map(post => post.id)) + 1 : 1;
globalStore.setState({
posts: [
{
id: newId,

제가 보고 구현해본 내용은 다음과 같은데 테스트 코드는 통과하지만 개인적으로 마음에는 안드는 구조예요 ㅎㅎ,, 준만님께서 더 고민해보시면 좋을 것 같습니다:)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

정말 비슷한 코드를 원정님 커밋에서 봤는데, 제가 거기에 대안으로 코드를 작성해봤어요. 한번 보시고 이것도 리뷰해 주시죵 ㅎㅎ

id: posts.reduce((acc,cur)=> {
       if(acc < cur) return cur;
       return acc;
     },0) + 1;

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[] 빈배열이 들어가도 1이 나오고, 가장 큰 값에 +1을 더하는 것은 동일합니다.

author: currentUser.username,
time: Date.now(),
content: document.getElementById("post-content").value,
likeUsers: [],
Comment on lines +18 to +20

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

document.getElementById로 ID를 하드코딩하는 것보다 event를 prop으로 받아서 event.target으로 접근하는 방법이 더 안정적일 것 같은데 어떻게 생각히사는지요?!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

어떤식으로 해야할지 살짝 감이 오지 않습니다.
참고할 커밋 있을까요?
내일 같이 코드리뷰 시간에 얘기해보면 좋을 것 같아요!

},
...posts,
],
});
};
return (
<div className="mb-4 bg-white rounded-lg shadow p-4">
<textarea
Expand All @@ -12,6 +33,7 @@ export const PostForm = () => {
<button
id="post-submit"
className="mt-2 bg-blue-600 text-white px-4 py-2 rounded"
onClick={onClickAddPost}
>
게시
</button>
Expand Down
60 changes: 58 additions & 2 deletions src/lib/createElement.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,61 @@
import {
checkNullishExceptZero,
replaceIfPropIsClass,
} from "../utils/commonUtils";
import { addEvent } from "./eventManager";
// import { addEvent } from "./eventManager";
junman95 marked this conversation as resolved.
Show resolved Hide resolved

export function createElement(vNode) {}
export function createElement(vNode) {
if (typeof vNode === "function" || typeof vNode?.type === "function") {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

normalizeVNode.jsvNode.type === "function"를 통해
컴포넌트 함수는 다 처리된 후 createElement로 전달될 것 같은데 혹시 한번 더 체크하시는 이유가 있으신지, 예외케이스가 있으셨는지 궁금합니다!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오~ 예리하십니다.
저희가 정규화 단계에서 모든 함수 호출을 하기때문에 해당 조건문에 걸릴 일이 없을 것 같네요.
예리한 피드백 감사합니다.

throw new Error("NEED NORMALIZE");
}
if (typeof vNode === "boolean" || !checkNullishExceptZero(vNode)) {
return document.createTextNode("");
}

function updateAttributes($el, props) {}
// 문자열 또는 숫자인 경우 텍스트 노드로 처리
if (typeof vNode === "string" || typeof vNode === "number") {
return document.createTextNode(String(vNode));
}

// 배열로 들어온 경우 fragment로 처리
if (Array.isArray(vNode)) {
const frag = document.createDocumentFragment();
vNode.forEach((node) => {
frag.append(makeElement(node));
});
return frag;
}
Comment on lines +21 to +27
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기서 vNode가 배열일 경우 createElement가 아니라 makeElement로 처리해주셨는데, 배열 요소에 vNode === "string" || vNode === "boolean"요런 케이스에 해당하는 요소가 올 경우에 오류가 발생하지 않으셨는지 궁금합니다!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

진짜 문제의 소지가 있겠네요!!
리뷰 감사합니다. 고쳐야될 코드같아요!!


// 그 외의 경우 element로 처리
const el = makeElement(vNode);

if (Array.isArray(vNode.children)) {
vNode.children.forEach((child) => {
el.appendChild(createElement(child));
});
}

return el;
}

function makeElement(vNode) {
const el = document.createElement(vNode.type);
updateAttributes(el, vNode.props);
return el;
}

/**
*
* @param {HTMLElement} $el
* @param {object} props
*/
function updateAttributes($el, props) {
for (const prop in props) {
if (prop?.startsWith("on")) {
addEvent($el, prop.slice(2).toLowerCase(), props[prop]);
continue;
}
$el.setAttribute(replaceIfPropIsClass(prop), props[prop]);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

개인적으로는 replacePropIsClass로 별도의 함수로 뺐다면, 윗줄에서 한 번 실행하고 반환값을 받아서 prop을 인자로 넘겨주는 쪽이 가독성이 더 좋을 것 같아요!

}
}
Comment on lines +52 to +60

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updateAttributes 함수를 보면 기존 코드는 모든 속성을 매번 설정하고 있어서 DOM 요소에 이미 설정된 속성이라도 변경 없이도 불필요하게 다시 설정되고 있네요 혹시 맞나용??

Suggested change
function updateAttributes($el, props) {
for (const prop in props) {
if (prop?.startsWith("on")) {
addEvent($el, prop.slice(2).toLowerCase(), props[prop]);
continue;
}
$el.setAttribute(replaceIfPropIsClass(prop), props[prop]);
}
}
function updateAttributes($el, props, prevProps = {}) {
for (const key in prevProps) {
if (!(key in props)) {
$el.removeAttribute(key);
}
}
for (const key in props) {
if (props[key] !== prevProps[key]) {
if (key.startsWith('on')) {
addEvent($el, key.slice(2).toLowerCase(), props[key]);
} else if (key === 'className') {
$el.setAttribute('class', props[key]);
} else {
$el.setAttribute(key, props[key]);
}
}
}
}

제가 개선해보면서 addEvent 함수를 호출하기 전에 이벤트 리스너 속성이 변경되었는지 확인하는 로직까지 한번 추가 해봤는데 요렇게 추가로 해보시는건 어떤가요?? 조금 더 고도화 하면 좋을 것 같습니다 :) 이 함수의 역할이 잘 보이게 간결하게 잘 작성하신 것 같아요!! 좋습니다 👍

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 코드 개선까지 감사합니다.

여기서 궁금한게,
props[key] !== prevProps[key] 코드에서 핸들러 함수가 상이한 것을 체크할 수 있나요??

9 changes: 8 additions & 1 deletion src/lib/createVNode.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
import { recursiveFlatten } from "../utils/arrayUtils";
import { checkNullishExceptZero } from "../utils/commonUtils";

export function createVNode(type, props, ...children) {
return {};
const flattenedChildren = recursiveFlatten(children, (val) => {
return checkNullishExceptZero(val);
});

return { type, props, children: flattenedChildren };
}
18 changes: 18 additions & 0 deletions src/lib/elementObserver.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export function createElementChildRemoveObserver(elements, helper) {
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === "childList") {
mutation.removedNodes.forEach((node) => {
console.log(node, "removed");
helper(node);
});
}
});
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이번 과제에서는 따로 사용되지 않아보이는데 코드를 읽어보면 도입하시려는 목적이 DOM 요소들의 자식 요소가 제거되고 나서, 각 제거된 요소에 대해 따로 이벤트 처리를 하시려고 한 것 같네요 어떤 목적으로 구현하려고 하셨나요??? 궁금합니다👀👀

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아~ MutationObserver를 통해서 최상위 엘리먼트 root 혹은 body등에서 삭제되는 순간 oldVNode를 초기화 해 주기 위해 넣은 코드입니다. 하지만 코드 개발 과정에서 초기화 과정이 불필요하다고 판단하여(오히려 성능 저하시킴)
뺴게 되었습니다!!

초기에 넣게된 이유는 테스트코드에서 before,afterEach등의 동작에서 자꾸 dom을 초기화 하는데 이부분 때문에 이슈가 난다고 판단해서 해당 타이밍을 포착해 다 초기화 해주려고 한 것이었습니다.


for (const element of elements) {
observer.observe(element, { childList: true, subtree: false });
}

return observer;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 함수는 더이상 사용되지 않는 함수인가요??

처음 들었을때 아이디어는 매우 좋다고 생각했는데 아쉽네요 ㅠ

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

디버깅하면서 없앴는데 다시한번 붙여봐야겠네요 ㅋㅋ

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제가 다시 붙여보려 했는데, 사실 상위 엘리먼트에서 요소를 지운다 하더라도 oldVNode를 무조건 초기화 하는 것은 좋지 않을 것 같아요.
가령 layout이 같은 두개의 다른 페이지(프로필, 홈)를 오갈때, updateElement가 아닌 createElement를 시키는 것이 더 비효율적일 테니까요!
MutationObserver 자체는 충분히 사용 방안에 대해서 공부해볼만한 좋은 WebAPI 인 것 같습니다.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

덕분에 최적한 방안에 대해서 생각해 볼 수 있어 좋았습니다. 감사해요!

48 changes: 45 additions & 3 deletions src/lib/eventManager.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,47 @@
export function setupEventListeners(root) {}
/**
* globalEvents
* {
* click: {
* element: handler
* }
* }
*/

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

export function removeEvent(element, eventType, handler) {}
// 콜백으로 넣으면 add할때의 remove할때의 콜백이 다를 수 있음
/**
*
* @param {HTMLElement} root
*/
export function setupEventListeners(root) {
for (const eventType in globalEvents) {
root.removeEventListener(eventType, handleGlobalEvents);
root.addEventListener(eventType, handleGlobalEvents);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혹시 updateAttributes에서도 removeEvent를 해주는데 root에서 한 번 더 삭제해주는 이유가 있을까요??

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

와 진짜 좋은 피드백인것 같습니다.

제 설계의도는 속성 업데이트 시 명령적으로 제거하는 것이 맞습니다.
위 코드는 이전에 근백님과 함께 디버깅 하면서 근백님의 코드 스타일을 제가 고려 없이 그대로 반영하다가 들어간 코드입니다.

예리한 피드백 감사합니다!

}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제가 @chb6734 분의 코드를 함께 보다가 setupEventListeners 함수에 필요할 때만 이벤트 리스너를 등록하도록 처음 등록할때만 이벤트를 추가하도록 하는것도 좋을 것 같아요~!
현재 코드에서는 이벤트 리스너를 제거한 다음 다시 추가하는 로직을 구현하고 있는데 처음 등록할때에만 추가하는 것도 좋을 것 같습니다

Suggested change
export function setupEventListeners(root) {
for (const eventType in globalEvents) {
root.removeEventListener(eventType, handleGlobalEvents);
root.addEventListener(eventType, handleGlobalEvents);
}
}
export function setupEventListeners(root) {
for (const eventType in globalEvents) {
if (!root.getAttribute(`data-listener-${eventType}`)) {
root.addEventListener(eventType, handleGlobalEvents);
root.setAttribute(`data-listener-${eventType}`, 'true');
}
}
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 필요할때만 초기화 하는 좋은 코드네요
참고해서 구현해보겠습니다!!

export function handleGlobalEvents(e) {
if (globalEvents[e.type].has(e.target)) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eventManager에서 중복된 이벤트 이슈를 어떻게 해결해야할지 감이 안잡혔는데, 준만님 코드를 보고 많이 배웠습니다!👍👍👍

globalEvents[e.type].get(e.target)(e);
}
}

export function addEvent(element, eventType, handler) {
if (!element || typeof handler !== "function") return;

globalEvents[eventType] = globalEvents[eventType] || new WeakMap();
globalEvents[eventType].set(element, handler);
}

export function removeEvent(element, eventType, handler) {
if (globalEvents[eventType].get(element) === handler) {
globalEvents[eventType].delete(element);
}
}

export function clearEvents() {
globalEvents = {};
}

Comment on lines +47 to +49

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 함수의 목적이 전역 globalEvents 객체를 완전히 초기화 하는 역할을 하고 있는 것 같아요!

이렇게 globalEvents = {}; 구현해주셔도 좋은데 개별 이벤트 유형에 대해 이벤트 리스너를 제거하도록 각각 세부적으로 구현해보는 것도 좋은 것 같아요 :)

Suggested change
export function clearEvents() {
globalEvents = {};
}
export function clearEvents() {
for (const eventType in globalEvents) {
const eventMap = globalEvents[eventType];
eventMap.forEach((_, element) => {
if (element && element.removeEventListener) {
element.removeEventListener(eventType, handleGlobalEvents);
}
});
delete globalEvents[eventType];
}
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 직접 순회하면서 지워주면 어떤 이점이 있을까요??

window.__myEventListeners = globalEvents;
31 changes: 30 additions & 1 deletion src/lib/normalizeVNode.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,32 @@
import { checkNullishExceptZero } from "../utils/commonUtils";

// falsy 값은 빈 문자열로 변환
// 숫자, 문자 = > 문자열로 변환
// 함수 => 함수 실행 결과로 변환

// vNode : {type , props, children}
export function normalizeVNode(vNode) {
return vNode;
if (typeof vNode === "boolean" || !checkNullishExceptZero(vNode)) {
return "";
}

if (typeof vNode === "number" || typeof vNode === "string") {
return String(vNode);
}

if (typeof vNode === "object") {
if (typeof vNode.type === "function") {
return normalizeVNode(
vNode.type({ ...vNode.props, children: vNode.children }),
);
}
junman95 marked this conversation as resolved.
Show resolved Hide resolved

return {
type: vNode.type,
props: vNode.props,
children: vNode.children
.map(normalizeVNode)
.filter(checkNullishExceptZero),
};
}
}
36 changes: 32 additions & 4 deletions src/lib/renderElement.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,38 @@
import { setupEventListeners } from "./eventManager";
import { createElement } from "./createElement";
import { setupEventListeners } from "./eventManager";
import { normalizeVNode } from "./normalizeVNode";
import { updateElement } from "./updateElement";

let oldVNode = null;

/**
* @description VNode를 실제 DOM 엘리먼트로 렌더링하는 함수
* @param {*} vNode createVNode 함수를 통해 생성된 VNode
* @param {HTMLElement} container 실제 DOM 엘리먼트(해당 VNode를 렌더링할 대상)
*/
export function renderElement(vNode, container) {
// 최초 렌더링시에는 createElement로 DOM을 생성하고
// 이후에는 updateElement로 기존 DOM을 업데이트한다.
// 렌더링이 완료되면 container에 이벤트를 등록한다.
if (!vNode) {
return;
}
// step1. vNode 정규화
const newVNode = normalizeVNode(vNode);

// step2. 정규화된 vNode를 기반으로 DOM 생성 또는 업데이트
if (!oldVNode) {
// step3. DOM 추가
const element = createElement(newVNode);
container.append(element); // 첫 렌더링 때만 container에 DOM 추가
} else {
// step3-diff. DOM 업데이트
if (container.firstChild) {
updateElement(container, newVNode, oldVNode);
} else {
const element = createElement(newVNode);
container.append(element);
}
}
// step4. 이벤트 핸들러 등록
setupEventListeners(container);
// step5. oldVNode 업데이트
oldVNode = newVNode;
}
junman95 marked this conversation as resolved.
Show resolved Hide resolved
Loading
Loading