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

[4팀 장철희] [Chapter 1-2] 프레임워크 없이 SPA 만들기 #32

Open
wants to merge 32 commits into
base: main
Choose a base branch
from

Conversation

jch1223
Copy link

@jch1223 jch1223 commented Dec 26, 2024

과제 체크포인트

기본과제

가상돔을 기반으로 렌더링하기

  • createVNode 함수를 이용하여 vNode를 만든다.
  • normalizeVNode 함수를 이용하여 vNode를 정규화한다.
  • createElement 함수를 이용하여 vNode를 실제 DOM으로 만든다.
  • 결과적으로, JSX를 실제 DOM으로 변환할 수 있도록 만들었다.

이벤트 위임

  • 노드를 생성할 때 이벤트를 직접 등록하는게 아니라 이벤트 위임 방식으로 등록해야 한다
  • 동적으로 추가된 요소에도 이벤트가 정상적으로 작동해야 한다
  • 이벤트 핸들러가 제거되면 더 이상 호출되지 않아야 한다

심화 과제

1) Diff 알고리즘 구현

  • 초기 렌더링이 올바르게 수행되어야 한다
  • diff 알고리즘을 통해 변경된 부분만 업데이트해야 한다
  • 새로운 요소를 추가하고 불필요한 요소를 제거해야 한다
  • 요소의 속성만 변경되었을 때 요소를 재사용해야 한다
  • 요소의 타입이 변경되었을 때 새로운 요소를 생성해야 한다

2) 포스트 추가/좋아요 기능 구현

  • 비사용자는 포스트 작성 폼이 보이지 않는다
  • 비사용자는 포스트에 좋아요를 클릭할 경우, 경고 메세지가 발생한다.
  • 사용자는 포스트 작성 폼이 보인다.
  • 사용자는 포스트를 추가할 수 있다.
  • 사용자는 포스트에 좋아요를 클릭할 경우, 좋아요가 토글된다.

과제 셀프회고

비록 과제였지만 react 초기 부터 중요 개념 중 하나인 virtual dom을 구현해보았다. 아쉬운 점은 과제를 통과하는 것에 급한 나머지 개념적인 부분을 더 딥다이브하지 못한 것 같다. 과제 통과에 너무 매몰되지 말고 시간 분배를 적절히 해서 개념적인 부분도 보충할 수 있도록 해야겠다.
과제를 진행하면서 객체를 다루는 일이 많았는데, 재귀함수에서 다루다 보니 런타임에러가 꽤 빈번하게 발생하면서 타입스크립트를 사용하는 이유 중 하나를 체감하였다. 타입스크립트 였다면 타입 정의하다가 머리 깨졌을지도 있지만 휴먼 에러를 방지하는 것이 더 DX가 좋아지는 것 같다.

기술적 성장

createVNode를 로직을 구현하면서 jsx를 어떻게 트랜스파일하는지 궁금증이 생기게 되었습니다.
조사를 해보니 vite의 esbuild는 babel 없이도 jsx를 사용할 수 있는데,
jsxFactory의 기본 값이 React.createElement를 사용하는 것으로 동작하기 때문이라고 이해하였습니다.

따라서 react를 설치하지 않았을 경우에는 동작하지 않는데,

export default mergeConfig(
  defineConfig({
    esbuild: {
      jsxFactory: "createVNode",
    },
    optimizeDeps: {
      esbuildOptions: {
        jsx: "transform",
        jsxFactory: "createVNode",
      },
    },
  }),
  defineTestConfig({
    test: {
      globals: true,
      environment: "jsdom",
      setupFiles: "./src/setupTests.js",
      exclude: ["**/e2e/**", "**/*.e2e.spec.js", "**/node_modules/**"],
    },
  }),
);

과제에 설정한 것 처럼 jsxFactory에 함수를 정의해서 jsx 변환 때 사용이 되도록 할 수 있습니다.
파일별로 구성하려면 // @jsx createVNode와 같이 주석을 사용하여 구성할 수도 있습니다. JSX가 automatic으로 설정된 경우에는 이 설정이 적용되지 않습니다.
automatic이 아닐 때 // @jsx createVNode를 jsx파일에 넣지 않으면 트랜스파일을 하지 못하는 것을 확인 했습니다.

코드 품질

학습 효과 분석

  • virtual dom이 무엇이고, diffing 알고리즘을 통해서 리액트에서 어떤 동작을 수행하는지 막연하게 알고 있었는데, 실제 동작을 구현하는 것은 생각보다 쉽지 않았습니다. 돌이켜 보면 알고 있던 개념을 토대로 어떻게 구현을 할까 고민하고 코딩한 것이 아니라 테스트코드를 통과하기 위해 코딩을 했던 것 같아서 아쉬움이 있습니다. 앞으로 어떤 개념을 구현 할 때 어떻게 코딩할지 좀 더 명확하게 정하고 코딩하는 연습을 해야 겠다는 생각이 들었습니다.

리뷰 받고 싶은 내용

  • 코멘트 추가 하였습니다.
  • 재귀함수를 디버깅 할 때 console.log를 통해서 디버그 터미널과 브라우저 개발자 도구에서 console값을 확인하며 디버깅을 했습니다. 그러다 보니 제가 원하는 부분 외에도 console이 찍혀서 어려움이 있었는데 디버깅 노하우가 있다면 알고 싶습니다!

@jch1223 jch1223 marked this pull request as draft December 26, 2024 11:08
@jch1223 jch1223 changed the title [4조] 장철희 [4팀 장철희] [Chapter 1-2] 프레임워크 없이 SPA 만들기 Dec 26, 2024
@jch1223 jch1223 marked this pull request as ready for review December 26, 2024 22:37
* }
* }
*/
const eventListeners = new Map();
Copy link
Author

Choose a reason for hiding this comment

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

처음에는 단순하게 eventListeners를 배열로 만들고, addEvent할 때 event 등록에 필요한 값들을 push해서 관리하려고 했습니다.
배열로 관리하게 될 경우 removeEvent함수에서 특정 이벤트를 제거하기 위해 eventListeners를 계속 순회해야하는 로직이 필요한데, 등록된 이벤트가 많아질 수록 계속 순회하는 것은 좋지 않을 것 같아서 eventListeners의 구조를 중첩된 Map형태로 변경하였습니다.

Comment on lines +7 to +33
const { loggedIn, currentUser, posts } = globalStore.getState();

const handleLikeClick = () => {
if (!loggedIn) {
alert("로그인 후 이용해주세요");
return;
}

let newLikeUsers = [];
if (!likeUsers.includes(currentUser.author)) {
newLikeUsers = [...likeUsers, currentUser.author];
} else {
newLikeUsers = likeUsers.filter((user) => user !== currentUser.author);
}

globalStore.setState({
posts: posts.map((post) => {
if (post.id === id) {
return {
...post,
likeUsers: newLikeUsers,
};
}
return post;
}),
});
};
Copy link
Author

Choose a reason for hiding this comment

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

전역 상태관리를 하고 있고, click에 대한 로직이 페이지에 있는 것보다 post 컴포넌트에 있는 것이 관심사가 맞다고 생각하여
로직에 필요한 값들(loggedIn, currentUser, posts)을 pops로 받지않고 스토어로 가지고 왔습니다.
props받지 않는 이유에 대한 적절한 논리가 될 수 있을까요?

Comment on lines +93 to +94
const newVNodeChildren = mergeConsecutiveStrings(newVNode.children ?? []);
const oldVNodeChildren = mergeConsecutiveStrings(oldVNode.children ?? []);
Copy link
Author

Choose a reason for hiding this comment

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

children이 ["좋아요", "0"]일 때, 엘리먼트에 "좋아요 0"이 되어야 하지만, 엘리먼트에 "0"만 노출되고 있었습니다. 금요일 새벽에 과제마감까지 시간이 얼마 남지 않아서 급하게 mergeConsecutiveStrings함수를 추가하였습니다.
어거지로 떼운 느낌이어서 개선을 하고 싶은데.. 어떤 방향으로 하는 것이 좋을까요?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant