Skip to content

solbijae/front_5th_chapter1-2

 
 

Repository files navigation

과제 체크포인트

배포 링크

https://solbijae.github.io/front_5th_chapter1-2/index.hash.html#/

기본과제

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

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

이벤트 위임

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

심화 과제

1) Diff 알고리즘 구현

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

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

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

❗[테스트 코드 오류잡기] '"/" 경로로 접근하면 홈 페이지가 렌더링된다’

appendChildren을 사용했었지만, replaceChildren로 변경하였습니다.

  1. 기존 노드를 효율적으로 제거하고 새로 렌더링 replaceChildren을 사용하면 container 내부의 기존 모든 자식을 제거한 후 새로운 요소를 삽입할 수 있습니다. 이를 통해 불필요한 appendChild 호출을 방지하고, 기존 노드를 하나씩 삭제하는 로직 없이 한 번의 호출로 변경할 수 있어 코드가 간결해집니다.

  2. 메모리 관리 및 성능 최적화 기존 DOM 노드를 유지하면서 appendChild를 사용할 경우, 필요 없는 노드가 남아 있을 가능성이 있으며, 이를 수동으로 정리해야 하는 추가적인 코드가 필요할 수 있습니다. 반면, replaceChildren은 자동으로 기존 내용을 정리하므로 메모리 누수를 방지하고 불필요한 DOM 조작을 줄이는 효과가 있습니다.

  3. 불필요한 이벤트 리스너 제거 이벤트 리스너가 등록된 요소를 직접 변경하는 경우, 기존 요소에 남아 있는 이벤트 리스너가 예기치 않은 동작을 일으킬 수 있습니다. replaceChildren을 사용하면 기존 요소가 완전히 제거되므로, 불필요한 이벤트 리스너가 남아 있지 않도록 할 수 있습니다.

  4. 일관된 상태 유지 UI를 갱신할 때, 기존의 DOM을 조작하는 방식보다 전체 컨테이너 내용을 교체하는 것이 명확한 상태 관리를 가능하게 합니다. 이는 가상 DOM 기반 렌더링을 구현할 때 불필요한 비교 연산을 줄이는 데도 도움이 됩니다.

✅ 결론 replaceChildren을 사용하면 기존 요소를 깔끔하게 제거하고 새로운 요소로 교체할 수 있어 코드를 간결하게 유지하면서도 성능과 유지보수성을 향상시킬 수 있습니다. 특히 초기 렌더링 시 불필요한 DOM 조작을 줄이고, 이벤트 리스너 관리 측면에서도 유리합니다.

❗[테스트 코드 오류잡기] null, undefined, boolean 값은 빈 문자열로 변환되어야 한다

처음에는 boolean 타입을 왜 걸러야하는지, 어떤 경우가 boolean 타입이 되는지 이해하지 못했습니다. boolean 타입이 될 수 있는 경우는 아래와 같다는걸 알게되었습니다. Image

❗[테스트 코드 오류 잡기] Virtual DOM과 이벤트 관리

  • 처음에는 변경되는 vNode에 대해서만 가상돔을 신경 썼는데, 오류 내용을 보니 updateElement에 previousVNode를 <div class="bg-gray-100 min-h-screen flex justify-center">...</div>이렇게 리얼돔 형태로 보내주고 있었습니다.
  • 생각해보니 비교를 하려면 previousVNode도 가상돔의 형태여야 하기 때문에 렌더링이 종료된 후 previousVNode = normalizedVNode;를 통해 previousVNode를 정규화된 노드로 업데이트할 수 있도록 하였습니다.

과제 셀프회고

과제 피드백 & 학습 효과 분석

  • 이번 과제는 어떻게 만들어야겠다는게 머릿속에 잘 그려지지 않아서 사전 학습자료와 AI툴을 이용해서 최대한 빠르게 개발 → 나만의 학습 가이드를 만들겠다는 계획을 가지고 임했습니다.
  • 처음에는 AI 툴을 사용해서 개발을 하기 때문에 배우는게 적지 않을까 고민되었는데 과제를 완료하고, 학습 가이드를 만들다보니 심도 깊게 과제를 고민해볼 수 있었습니다.
  • 🆕 새로 만들어본 나만의 학습 가이드

기술적 성장

  • 리액트를 공부할 때 가장 기본이 되지만, 생각해보지 못했던 VirtualDOM에 대해 자세히 알게된 점이 가장 좋았습니다. 직접 구현을 해보니 리액트에서 당연하게 생각했던 부분을 실제로 구현해 사용하려면 복잡하고, 고려해야할 부분이 많다는 것을 알게 되었습니다.
  • 개요
    • Virtual DOM은 실제 DOM의 '가벼운' 복사본이며, 객체로 관리되어 개발자 경험이 향상됨.
    • 직접 DOM 조작이 가장 빠르지만, Virtual DOM을 활용하면 성능 이슈를 방지 가능.
    • 선언형 UI를 지원하여 전체적인 구조 이해가 쉬워지고, HTML 조작을 간소화함.
    • 브라우저 종속성이 없으며, JSX를 사용하여 가독성을 개선함.
  • 동작 원리
    1. Virtual DOM은 메모리 상에서 JavaScript 객체로 표현됨.
    2. 상태 변경 시 새로운 Virtual DOM을 생성하여 이전 버전과 비교(diffing).
    3. 변경된 부분만 실제 DOM에 반영(Reconciliation).
    4. 이를 통해 성능 최적화 및 불필요한 DOM 조작 방지.
  • Virtual DOM이 만능일까?
    • 작은 변경이 많을 경우 오히려 오버헤드가 발생할 수 있음.
    • 복잡한 애니메이션 처리에는 한계가 있음.
    • Virtual DOM보다 특정 상황에서는 직접 DOM 조작이 더 빠를 수 있음.

코드 품질

  • 이번에는 내용을 이해하고 소화하기 벅찼습니다. 그래서 평소와는 다르게 코드에 주석을 많이 활용해 내용의 흐름을 놓치지 않도록 노력했습니다.

리뷰 받고 싶은 내용

질문 1) oldComponent 재사용 로직을 더 최적화할 방법이 있을까요? 현재 oldNode.type과 newNode.type을 비교하여 동일한 경우에만 oldComponent를 재사용하고 있습니다. 이 방식에서는 다음과 같은 고민이 있습니다.

  1. 상태(state) 및 내부 데이터 유지
  • 기존 VNode를 재사용하더라도, 새로운 nextVNode를 생성하는 과정에서 내부 상태가 유지되지 않을 가능성이 있습니다.
  • props 변화가 크지 않은 경우에도 불필요한 재생성이 발생할 수 있습니다.
  1. 불필요한 연산 방지
  • 현재 방식에서는 항상 newNode.type({...})을 호출하여 새로운 VNode를 생성하고 있습니다.
  • props가 변경되지 않았다면 기존 VNode를 그대로 유지하는 방식이 가능할지 궁금합니다.
  1. 비교 방식 개선
  • type이 같은 경우에도 props를 비교하여 변동이 없으면 기존 VNode를 유지할 수 있을지 고민하고 있습니다.
  • React.memo처럼 이전 props와 비교하여 변경된 경우에만 다시 렌더링하는 방식이 적용 가능할까요?
  // + 함수형 컴포넌트인 경우 비교 후 재사용
  if (typeof newNode.type === "function") {
    const oldComponent = oldNode.type === newNode.type ? oldNode : null;
    const nextVNode = newNode.type({
      ...newNode.props,
      children: newNode.children,
    });
    return updateElement(parentElement, nextVNode, oldComponent, index);
  }

답변: 말씀해주신 방향 모두 좋은 방식으로 접근하고 계신 것 같아요. 상태 유지의 경우 리액트에서는 Fiber 아키텍처를 통해 컴포넌트의 상태를 각 노드에 저장하고 있는데요. 이런 부분에 대한 구현을 살펴보셔도 좋을 것 같고 불필요한 연산에 대해서도 정확하게 메모이제이션에 대한 내용에 해당되어 구현이 가능할 것으로 보입니다. 캐싱 로직을 작성하면 될 것 같아요! 마지막으로 리엑트에서도 memo같은 곳에 파라미터를 전달해 비교 방식을 세밀하게 할 수 있는데요. 기본 방식을 따르게 하되, 메모처럼 파라미터를 받으면 좀 더 확장성 있는 인터페이스가 되지 않을까 싶네요 ㅎㅎ

질문2) 저희 회사에서는 각 모듈별로 이벤트 리스너를 달아주는데 이번 과제에서는 가이드대로 eventManager에서 모든 이벤트 타입에 대해 한 번만 리스너를 등록하는 방식으로 구현하였습니다.

이벤트 리스너가 자동으로 처리되는 방식이라 개발하기에는 더 수월한 것 같은데, 이벤트 버블링 오버헤드나, 이벤트 위임을 사용함에 따라 프로젝트 규모가 커지면 이벤트 흐름을 파악하기 어려운 등의 문제는 없을까요?

보통 어떤식으로 구현하는 것이 일반적인지 궁금합니다!

답변: 우선 위임을 했을 때 메모리에 대한 부분이나 동적인 요소에 대한 관리 부분에서 편하다는 부분을 아셨을 것으로 보이는데요! 보통 위임 방식을 구현하게 되는 경우 위임을 받은 부분에 대해 명확하게 디버깅 할 수 있는 방법이 필요하긴 한것 같아요! 사실 위임을 했다고 모든 장점을 누렸다라고 보기엔 어려운게 위임 후 위임받은 곳에서 추가적인 최적화들이 필요해 보이거든요. 리액트에서 실제로 어떻게 구현이 되어있는지 살펴보면 도움이 될 것 같은데요. 관련해서 도움이 될 수 있는 글 함께 공유 드릴게요! (최근에 아고라에 도운님도 글 올려주셨더라구요! 그것도 꼭 먼저 읽어보세요)

About

항해플러스 챕터 1-2 과제

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • JavaScript 98.8%
  • HTML 1.2%