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

[6팀 남궁철] [Chapter 1-2] 프레임워크 없이 SPA 만들기 #3

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

Conversation

nkcfe
Copy link

@nkcfe nkcfe commented Dec 21, 2024

과제 체크포인트

기본과제

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

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

이벤트 위임

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

심화 과제

1) Diff 알고리즘 구현

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

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

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

과제 셀프회고

기술적 성장

  • 새로 학습한 개념
    • 이벤트 위임의 장점
      • 이벤트 핸들러 관리가 용이하다.
      • 동적으로 추가된 요소에도 이벤트 처리를 할 수 있다.
      • root에서만 추가 제거를 하면 되기때문에 메모리의 낭비를 줄일 수 있다.
    • React의 작동 방식
      • JSX -> babel -> createVDom -> normalize -> createElement -> updateElement 라는 플로우로 JSX가 어떻게 리얼돔으로 변경되는지 알 수 있었습니다.
    • 가상돔
      • 가상돔이 리얼돔을 객체 형식으로 표현했다는 것을 알 수 있었습니다.
      • updateElement를 작성하면서 diff 알고리즘이 어떻게 작동하는지 알 수 있었습니다.
    • 상태의 구독 및 변경을 알려주는 로직을 저번 주차에 실패했었는데 방법과 개념 및 상태관리, 업데이트의 플로우에 대해 확실히 이해할 수 있었습니다.
  • 기존 지식의 재발견/심화
    • javascript의 메모리 참조 개념
    • React가 가고자 했던 방향에 대한 생각
      • DX 향상 (상태 관리, DOM 직접 조작, UI 업데이트, 단방향 데이터 흐름 등등)
  • 구현 과정에서의 기술적 도전과 해결
    • eventManager를 사용하면서 handler들을 관리할 객체를 Map과 weakMap에서의 메모리 참조 부분에 대해서 다시 공부할 수 있었습니다. Map 사용 시 eventManager에서 handler를 제거해도 여전히 참조하고 있어 GC가 동작하지 않아 오류가 발생하는 경우가 있어고 이를 weakMap을 활용해서 이벤트 제거시 메모리를 참조하지 않게 하는 방법으로 해결했습니다.
    • JSX -> Babel 이 변환 과정에 대해서 이해가 가질 않아 babel에 대해서 알아보았고 babel 공식 홈페이지에서 변환기를 사용해보고 어떤식으로 데이터가 변환이 되는지 알 수 있었고 createVNode를 작성할 수 있었습니다.

코드 품질

  • 특히 만족스러운 구현

    • 이벤트 핸들러 관리
      const eventHandlers = new WeakMap();

      • 핸들러 제거시 메모리를 자동 관리하고 많은 수의 핸들러를 관리하기도 용이하고 동적 요소를 처리하기도 좋은 자료구조라고 생각합니다.
    • 이벤트 위임 및 버블링 구현

      export function setupEventListeners(root) {
      // 이벤트 타입 반복
      Object.values(EVENT_TYPES).forEach((eventType) => {
      // 이벤트 위임 설정
      root.addEventListener(eventType, (e) => {
      // 실제 이벤트가 발생한 요소 가져오기
      let target = e.target;
      
            // 버블링 처리
            while (target && target !== root) {
              const handlers = eventHandlers.get(target);
      
              // 이벤트 핸들러 실행
              if (handlers?.[eventType]) {
                handlers[eventType](e);
                if (e.cancelBubble) break;
              }
              target = target.parentElement;
            }
          });
      
      });
      }```
      
  • 리팩토링이 필요한 부분

    • @updateElement.js
      현재 diff 알고리즘에서는 단순 비교만 수행하고 있습니다, 하지만 React에서는 key를 활용해서 더 효율적인 리스트를 비교하는 방법으로 작동한다는 것이 생각나서 이 부분들을 적용해보면 React에 대해서 더 상세한 이해가 되지 않을까 하는 생각이 있습니다.
  • 코드 설계 관련 고민과 결정

    • @renderElement.js
      이벤트 리스너를 초기 설정을 어디에서 하는게 좋을까 하는 고민이 많았습니다. 원래는 render.js에서 하는게 맞지 않나 하는 생각이 있었으나, 테스트가 독립적으로 구성되어 통과를 못하는 사례가 있었고, 컴포넌트의 라이프 사이클과 함께 이벤트핸들러가 관리되어야 할 것 같다는 고민으로 renderElement에서 초기 설정을 하도록 구성했습니다.

학습 효과 분석

  • 가장 큰 배움이 있었던 부분
    • JSX → VDOM → 실제 DOM 변환 과정에 대한 확실한 이해도가 생겼습니다.
    • diff 알고리즘의 작동 방식에 대해 알 수 있었습니다.
  • 추가 학습이 필요한 영역
    • diff 알고리즘에서 렌더링 최적화 방법에 대해 조금 더 알아보고싶습니다.
  • 실무 적용 가능성
    • Observer 패턴으로 설계에 적용해볼 수 있을 것 같습니다.
    • react에 대한 더 깊은 이해도를 기반으로 바라보는 관점이 달라지지 않았을까.. 하는 생각이 듭니다.

과제 피드백

  • 과제에서 모호하거나 애매했던 부분
  • 과제에서 좋았던 부분

리뷰 받고 싶은 내용

  1. 성능 최적화
  • 현재 구현한 diff 알고리즘이 얼마나 효율적인건지 정리하기가 어렵습니다.
    앞서 말한 리스트를 key방식으로 업데이트를 하는것 혹은 더 나은 diff 알고리즘 구현 방식이 있을까요?
  1. 설계 결정
  • 이벤트 위임 구현 시 WeakMap 사용이 적절한 선택이었을지를 모르겠습니다. 메모리 참조와 이벤트 핸들러 관리가 가장 용이한 것 같아 사용했습니다만 멘토님의 의견도 궁금합니다.
  1. 조건문 사용 방법
  • 조건문을 사용하다보면 항상 코드가 지저분해진다는 느낌을 받곤 하는데 현재 작성된 방법보다 조금 더 나은 방법이 있다면 알려주시면 감사하겠습니다.

export function createElement(vNode) {
  // null, undefined, boolean -> 빈 텍스트 노드 생성
  if (vNode === null || vNode === undefined || typeof vNode === "boolean") {
    return document.createTextNode("");
  }

  // string, number -> 텍스트 노드 생성
  if (typeof vNode === "string" || typeof vNode === "number") {
    return document.createTextNode(String(vNode));
  }

  // 배열 처리
  if (Array.isArray(vNode)) {
    // fragment 생성
    const fragment = document.createDocumentFragment();

    // 자식 노드 생성
    vNode.forEach((child) => {
      // 재귀적으로 자식 노드 생성
      const childElement = createElement(child);
      fragment.appendChild(childElement);
    });

    // fragment 반환
    return fragment;
  }

  // 객체일 경우 - 재귀적으로 자식 노드 생성
  const element = document.createElement(vNode.type);

  // props 처리
  if (vNode.props) {
    // 각 속성 처리
    Object.entries(vNode.props).forEach(([key, value]) => {
      // 이벤트 핸들러 처리
      // onClick, onMouseOver, onMouseOut -> click, mouseover, mouseout
      if (key.startsWith("on") && typeof value === "function") {
        addEvent(element, key, value);
      }
      // className 특별 처리
      // tailwind 클래스로 변환 처리를 위해 추가
      else if (key === "className") {
        element.setAttribute("class", value);
      }
      // 일반 속성 처리
      // children 제외
      else if (key !== "children") {
        element.setAttribute(key, value);
      }
    });
  }

  // 자식노드가 있을 경우 재귀적으로 호출
  if (vNode.children) {
    vNode.children
      .filter(Boolean) // falsy 값 제거
      .map(createElement) // 재귀적으로 자식 노드 생성
      .forEach((child) => element.appendChild(child)); // 자식 노드 추가
  }

  return element;
}

@nkcfe nkcfe changed the title Chore: 오너 설정 [6팀 남궁철] [Chapter 1-2] 프레임워크 없이 SPA 만들기 Dec 21, 2024
@developer-1px
Copy link

3
조건문 사용 방법
조건문을 사용하다보면 항상 코드가 지저분해진다는 느낌을 받곤 하는데 현재 작성된 방법보다 조금 더 나은 방법이 있다면 알려주시면 감사하겠습니다.

한 함수 내에서 조건문이 많은 건 문제가 되지 않습니다. depth가 1개이니 이 경우는 조건문은 문제없습니다. if문 내부의 코드가 복잡해진다면 코드로 변경할 수 있고, if의 괄호내의 조건이 복잡해진다면 이 역시 함수로 분리할 수 있습니다. 지금 코드 분량에서는 충분히 적절해보이며 배열처리, props처리 등등은 향후 코드가 길어진다면 함수로 분리하면 좋겠네요.

수고하셨습니다.

@devsuzy
Copy link

devsuzy commented Dec 28, 2024

코드 중간중간에 주석을 달아주셔서 코드 흐름 파악이 잘 되어 술술 읽혔습니다!
저도 구현하다보니 조건문 중첩이 많아져서 고민했었어요 ㅠ 그래도 철님께서 깔끔하게 작성하셔서 그런지 지저분하단 느낌은 안들어요!! 👍

// 이벤트 리스너 설정
export function setupEventListeners(root) {
// 이벤트 타입 반복
Object.values(EVENT_TYPES).forEach((eventType) => {

Choose a reason for hiding this comment

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

오... 이벤트 핸들러 문자열 처리할 때, 3번째 글자를 변경하지 않고도 처리할 수 있군요..!

@BongjoonKim
Copy link

주석이 잘 달려있어서 코드 파악이 편한 것 같아요! WeakMap을 왜 사용해야하는지 조금씩 이해되네요..!

이번주도 수고하셨습니닷!

child !== null && child !== undefined && typeof child !== "boolean",
);

// type가 함수일 경우 (class도 포함)

Choose a reason for hiding this comment

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

코드 주석 덕분에 이해하는 데 큰 도움이 되었습니다! createVNode는 단순히 VNode만 처리하는 역할을 한다고 합니다. normalizedVNode에 함수형을 처리하는 로직이 있다면 함수형을 처리하는 분기처리는 빼도 된다고 알고 있습니다!

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.

6 participants