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

[#26] 워크스페이스 페이지의 불필요한 리렌더링 개선 #35

Merged
merged 14 commits into from
Jan 24, 2025

Conversation

lee0jae330
Copy link
Member

@lee0jae330 lee0jae330 commented Jan 23, 2025

🔗 #26

🙋‍ Summary (요약)

  • CSS 툴팁 렌더링 시 발생하는 불필요한 리렌더링 해결
  • CSS 속성 편집 시 발생하는 불필요한 리렌더링 해결

😎 Description (변경사항)

CSS 툴팁 렌더링 시 불필요한 리렌더링 발생 해결

  • CSS 툴팁이 렌더링 될 경우 아래와 같이 모든 CSS 속성 선택 컴포넌트가 리렌더링되는 문제가 있었습니다.

BooLock--Chrome2025-01-2322-26-36-ezgif com-video-to-gif-converter

  • 해당 문제는 CSS 툴팁의 렌더링 위치를 결정하는 X, Y 좌표를 전역 상태로 관리하여 발생하는 문제였습니다.
문제의 코드
export const CssOptionItem = ({ cssItem, index }: CssOptionItemProps) => {
  const { totalCssPropertyObj, currentCssClassName } = useCssPropsStore();
  const { handleCssPropertyCheckboxChange, handleCssOptionChange, handleColorChange } =
    useCssOptions();

  const {
    cssOptionValue,
    isHover,
    indexOfHover,
    isChecked,
    cssOption,
    handleMouseEnter,
    handleEnterKey,
    handleMouseLeave,
    handleChangeInputValue,
  } = useCssOptionItem(cssItem);

  const { leftX, topY } = useCssTooltip(); // 전역 상태 사용
  • 모든 CssOptionItem 컴포넌트들이 전역 상태인 leftX, topY를 구독해서 발생하는 문제임을 확인했습니다.
  • 해결은 다음과 같이 했습니다.
  • tooltip의 좌표를 전역 상태가 아닌 local 상태로 관리하도록 변경했습니다.
  • 또한 기존 tooltip의 높이를 40이라는 매직넘버로 관리하던 방식에서 useLayoutEffect를 통해 화면 렌더링 전에 툴팁의 높이를 계산하는 방식으로 변경하여 40이라는 매직넘버를 없앨 수 있었습니다.
변경된 useCssTooltip
import { useLayoutEffect, useRef, useState } from 'react';

import { useWindowSize } from '@/shared/hooks';

export const useCssTooltip = (leftX: number, topY: number) => {
  const tooltipRef = useRef<HTMLDivElement | null>(null);
  const [tooltipHeight, setTooltipHeight] = useState<number>(0);
  const { screenHeight } = useWindowSize();

  useLayoutEffect(() => {
    if (tooltipRef.current) {
      setTooltipHeight(tooltipRef.current.getBoundingClientRect().height);
    }
    return () => setTooltipHeight(0);
  }, [tooltipRef.current]);

  let tooltipX = leftX;
  let tooltipY = 0;

  if (topY + tooltipHeight > screenHeight) {
    tooltipY = -topY + tooltipHeight;
  } else {
    tooltipY = topY;
  }

  return { tooltipX, tooltipY, tooltipRef };
};
변경된 CssTooltip 코드
import { createPortal } from 'react-dom';
import { useCssTooltip } from '@/shared/hooks';

type CssTooltipProps = {
  description: string;
  leftX: number;
  topY: number;
};

export const CssTooltip = ({ description, leftX, topY }: CssTooltipProps) => {
  const { tooltipX, tooltipY, tooltipRef } = useCssTooltip(leftX, topY);
  return createPortal(
    <div
      className={`text-gray-white text-tooltip-sm fixed z-[9999] rounded-3xl ${tooltipY >= 0 ? 'rounded-tl-none' : 'rounded-bl-none'} bg-green-500 px-3 py-2`}
      style={{
        left: `${tooltipX + 18}px`,
        top: tooltipY >= 0 ? `${tooltipY + 8}px` : `${-tooltipY}px`,
      }}
      ref={tooltipRef}
    >
      <p>{description}</p>
    </div>,
    document.body
  );
};
- 개선 결과

BooLock--Chrome2025-01-2323-08-10-ezgif com-video-to-gif-converter (1)

작업 내용 요약2

  • CSS 클래스 속성 편집, CSS클래스명 선택 시 워크스페이스 헤더, Workspace Workcontent 등의 컴포넌트가 리렌더링되는 문제가 발생했습니다.

BooLock--Chrome2025-01-2323-11-42-ezgif com-video-to-gif-converter

  • 문제의 원인은 CssPropsStore라는 전역 상태를 사용하는 모든 컴포넌트에서 잘못된 방식으로 상태를 구독하고 있어서 발생하는 문제였습니다.
const { totalCssPropertyObj } = useCssPropsStore();
  • 위 코드처럼 구조분해할당으로 상태 변수를 구독할 경우, useCssPropsStore((state) => state)의 결과 중에서 totalCssPropertyObj만 구독하는 것이 아닌, 내부적으로 CssPropsStore의 모든 상태를 구독해서 발생하는 문제 였습니다.
    • CssPropsStore에서 totalCssPropertyObj만 구독해야 하는 컴포넌트가 currentClassName처럼 필요하지 않는 상태들을 구독했고 이는 곧 의도치 않은 리렌더링이라는 결과를 낳았습니다.
  • 그래서 구조분해할당으로 상태를 구독하는 것이 아닌 아래와 같이 state에서 직접 원하는 상태만 반환하였습니다.
  const totalCssPropertyObj = useCssPropsStore((state) => state.totalCssPropertyObj);
  const currentCssClassName = useCssPropsStore((state) => state.currentCssClassName);
  • useCssPropsStore를 사용하는 모든 컴포넌트를 위와 같은 코드로 변경하니 불필요한 리렌더링을 방지할 수 있었습니다.

BooLock--Chrome2025-01-2323-22-03-ezgif com-video-to-gif-converter

🔥 Trouble Shooting (해결된 문제 및 해결 과정)

🤔 Open Problem (미해결된 문제 혹은 고민사항)

  • 추후에 zustand 사용법 관련해서 깊게 학습하고 문서화하겠습니다.

@lee0jae330 lee0jae330 self-assigned this Jan 23, 2025
@lee0jae330 lee0jae330 requested a review from a team as a code owner January 23, 2025 07:58
@lee0jae330 lee0jae330 requested review from Ujaa and chichoc and removed request for a team January 23, 2025 07:58
@lee0jae330 lee0jae330 changed the title [#26] 리렌더링 개선 [#26] 워크스페이스 페이지의 불필요한 리렌더링 개선 Jan 23, 2025
@inhachoi
Copy link
Contributor

inhachoi commented Jan 23, 2025

제가 승인자는 아니지만 글이 좋아서 읽다가 코멘트 남깁니다 ㅎㅎ
매직넘버 하다가 문득 든 생각인데,

let tooltipY = 0;

0도 매직넘버로 생각하고 상수 처리하는 대해서 어떻게 생각하시나요?
뭔가 지금까지는 0이 아닌 숫자들을 매직넘버로 자연스럽게 생각했던 것 같아서요.

고생 많으셨습니다 대 영 재 님 ❤️

@lee0jae330 lee0jae330 added the refactor 리팩토링 label Jan 23, 2025
@lee0jae330 lee0jae330 linked an issue Jan 23, 2025 that may be closed by this pull request
5 tasks
Copy link
Collaborator

@chichoc chichoc left a comment

Choose a reason for hiding this comment

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

리렌더링 개선하느라 고생하셨습니다~ 타입과 훅 분리 등 코드 구조도 개선하신 점 리스팩입니다 역시 대영재<3

Comment on lines +9 to +11
const currentCssClassName = useCssPropsStore((state) => state.currentCssClassName);
const totalCssPropertyObj = useCssPropsStore((state) => state.totalCssPropertyObj);
const selectedCssCategory = useCssPropsStore((state) => state.selectedCssCategory);
Copy link
Collaborator

Choose a reason for hiding this comment

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

구조분해할당을 지양하는 것이 베스트지만, 여러 상태를 불러와야할 경우 useShallow도 추천드립니당

@Ujaa
Copy link
Contributor

Ujaa commented Jan 24, 2025

구조 분해 할당이 문제였던 거군요..! 덕분에 zustand에 대해 조금 더 이해할 수 있었습니다. 감사합니다!

@lee0jae330 lee0jae330 merged commit 8e6a057 into dev Jan 24, 2025
5 checks passed
@lee0jae330 lee0jae330 deleted the refactor/26 branch January 24, 2025 06:45
Honghyeonji added a commit that referenced this pull request Jan 28, 2025
* 🔨 refactor: Docker 이미지 최적화를 위한 코드 리팩토링

* 🙀 chore: 오타 수정

* 🐛 fix: COPY 경로 수정

* 🐛 fix: swagger-auto 명령어 추가

* 🙀 chore: 오타수정

* 🐛 fix: backend 이미지가 base-image를 사용하지 않으므로 새로운 패키지 매니저 설치 및 추가 파일 복사하는 로직 추가

* 🐛 fix: pnpm-lock 복사 코드 추가

* 🐛 fix: 서버 패키지 설치 안되는 오류 해결하기 위해 코드 수정

* 🐛 fix: eslint 경로 체크용 package 폴더 추가 복사

* 🐛 fix: 의존성을 복사해오고 dev용을 지우는 방식으로 수정

* 🐛 fix: pnpm-workspace 파일 복사

* 🙀 chore: cicd 스크립트 오타 수정

* 🙀 chore: copy 명령어 합침

* 🙀 chore: 코드 구조 변경

* 🐛 fix: COPY 경로 수정

* 🙀 chore: 경로 오타 수정

* 🔨 refactor: 배포시 swagger-auto 안 하게 로직 수정 및 prod 종속성에 필요한 패키지 dev 종속성에서 이동

* 🙀 chore: 경로 오타 수정

* 🙀 chore: 안쓰는 라이브러리 삭제

* 🚨 !HOTFIX!: env 설정 누락된 코드 추가

* 🚨 !HOTFIX!: env 설정 누락된 코드 추가

* [#10] 홈페이지 가상스크롤 적용 (#14)

* ✨ feat: scroll의 Y 좌표를 리턴하는 useScroll 커스텀 훅 추가

* 🚚 rename: hooks/css에 있던 useWindowSize 훅 폴더 위치 변경

* ✨ feat: 가상스크롤 구현 중

* 🙀 chore: 함수명 오타 수정

* ✨ feat: 가상스크롤 커스텀훅 추가

* ✨ feat: WorkspaceGrid 컴포넌트에 가상스크롤 적용

* 🙀 chore: grid의 총 높이 계산할 때 반올림   적용

* [#11] Google Analytics 적용 (#15)

* ✨ feat: ga4 코드 추가

* 🔨 refactor: 로컬 환경 디버깅용 코드로 전환

* 🙀 chore: GA 디버깅 코드 삭제

* 🔨 refactor: workflow 에 dockerhub 사용하는 코드 제거

* ✨ feat: 이벤트 트레이싱

* 🔨 refactor: docker hub 로그인 부분 삭제

* 🔨 refactor: workflow와 compose 파일 dockerhub 쓰는 스크립트로 수정

* 🐛 fix: dockerhub 쓰는 버전에서는 build단계에서 pnpm 설치가 필요 없으니 해당 코드 삭제

* 🔨 refactor: deploy단계에서 docker hub 로그인 하는 부분 이동

* 🙀 chore: git 상태 확인하는 코드 추가

* 🙀 chore: docker compose cache_from 부분 삭제(dockerhub에서 가져올 땐 필요 없음)

* 🙀 chore: docker hub 로그인 방식 수정

* 🙀 chore: 안쓰는 옵션 삭제

* 🐛 fix: base-image 이름 변경

* 🙀 chore: 언더바 하이픈으로 수정

* 🙀 chore: 안쓰는 코드 삭제

* 🐛 fix: dockercompose 코드에서 build내에 image 잘 못 넣은 코드 수정

* 🙀 chore: docker 로그인 확인을 위한 docker info 코드 추가

* 🔨 refactor: docker compose에서 base 이미지 가져오는 것을 명시적으로 표시

* 🙀 chore: docker compose config 확인을 위한 코드 추가

* 🙀 chore: docker compose 체크를 위한 경로 수정

* 🙀 chore: 필요없는 코드 삭제

* 🐛 fix: compose 파일에서 base 부분 삭제

* 🔨 refactor: docker 캐시 이용하는 방법으로 변경

* 🔨 refactor: docker tag 설정해주는 코드 추가

* 🙀 chore: 코드 변경

* 🔨 refactor: github actions 캐시사용하는 방식으로 변경

* 🔨 refactor: docker build 코드 cache 가져오게 수정

* 🔨 refactor: buildx 코드 추가

* 🔨 refactor: docker buildx 사용하는 방식 수정

* 🔨 refactor: base 이미지에 로컬태그를 붙여주는 방식 대신 레지스트리 경로를 명시적으로 표시하는 방식으로 수정

* 🔨 refactor: docker 이미지 빌드 때 사용하는 cache from-to 원격주소로 변경

* 🔨 refactor: github action 캐싱키 변경 및 docker 이미지 빌드 시 cache 주소 잘못 쓴 것 수정

* 🙀 chore: docker image output 명시적으로 작성

* 🔨 refactor: base image를 hub에 push하지 않고 로컬에서 빌드해서 그대로 사용하는 방식으로 수정

* 🙀 chore: latest 태그 제거

* 🙀 chore: 베이스 이미지 빌드할 때 load: true 옵션 추가

* 🔨 refactor: base-image도 docker hub에 올리는 방법으로 재수정

* 🙀 chore: username 잘 나오는지 확인

* 🙀 chore: set up docker buildx 에서 with 설정 추가

* 🙀 chore: 캐시 초기화 코드 추가

* 🙀 chore: build-args 넣어주는 방식 수정

* 🙀 chore: build-args 넣어주는 방식 수정

* Update boolock-dev-cicd.yml

* Update boolock-dev-cicd.yml

* 🙀 chore: 디버깅용 코드 추가

* 🔨 refactor: base image 로컬 빌드 및 사용으로 방법 변경

* 🔨 refactor: cache-from 타입 inline으로 변경

* 🙀 chore: github actions 로컬에 base-image가 잘 남겨져 있는지 확인하는 코드 추가

* [#18] useQuery, useInfiniteQuery -> useSuspenseQuery, useSuspenseInfiniteQuery 교체 (#19)

* 🔨 refactor: useQuery를 useSuspenseQuery로 교체 및 error 처러 코드 삭제

* 🔨 refactor: 로딩 UI 및 에러 처리 코드 삭제

* 🔨 refactor: suspense 적용 및 error boundy fallback에 workspace 에러 페이지 설정

* 🔨 refactor: useInfinityQuery -> useSuspenseQuery로 교체

* 🙀 chore: 400 에러와 404에러의 에러 코드 및 상태 메세지가 잘못 설정 되어 있어 올바르게 수정

* 🐛 fix: 기존 에러 발생 시 500 에러로만 응답하는 문제 해결

* 🔨 refactor: workspaceContainer에서 워크스페이스 데이터를 렌더링하고 페칭하는 부분을 분리함

* 🙀 chore: 컴포넌트 분리에 따른 코드 수정

* 🙀 chore: github action 에서 파일명 변경을 감지하지 못한 문제로 인해 파일명 변경

* 🙀 chore: 불필요한 console.log 삭제

* 🔨 refactor: base-image cache 루트 변경

* 🔨 refactor: type=local로 재변경

* 🔨 refactor: base-image load단계 추가

* 🙀 chore: 이미지 태그 삭제

* 🙀 chore: 이전 코드에서 캐싱 잘 되는지 확인용

* 🙀 chore: dockerhub_username arg 추가

* 🔨 refactor: 이전버전에 cache 경로만 수정

* 🙀 chore: build-args 추가

* 🔨 refactor: buildx 플러그인 초기화 옵션 + cache에서 이미지 사용시 모든 캐시 데이터를 최대한 활용하는 mode=max 옵션 추가

* 🙀 chore: base-image 캐싱 추가

* 🔨 refactor: base image 로컬 저장 및 hub push 둘 다 되게 수정

* 🙀 chore: verify 스텝 추가

* [#21] 홈페이지 로딩 시 스켈레톤UI에 그리드가 적용되지 않는 문제 해결, 워크스페이스 로딩 화면 조건부 렌더링 (#22)

* 🐛 fix: 스켈레톤UI에 그리드가 안되는 문제 해결

* ✨ feat: 워크스페이스 로딩 시간이 0.05초 이상일 때만 로딩 화면이 렌더링되도록 변경

* 🙀 chore: 오타 수정

* 🙀 chore: 아ㅏㅏㅏㅏㅏㅏㅏㅏㅏㅏㅏㅏㅏㅏㅏㅏㅏㅏ

* 🔨 refactor: 원격에서 base-image 가져와서 load하는 방식으로 수정

* 🙀 chore: 경로 명시적으로 쓰기

* 🙀 chore: docker 네트워크 연결 테스트 코드 추가

* 🙀 chore: docker login 스텝 이동

* 🙀 chore: 토큰 코드 추가

* 🙀 chore: token 발급받는 부분 추가

* 🚨 !HOTFIX!: error가 발생할 때 return 해야하는데 그렇지 않을 때 return 하여 workspace가 초기화되지않는 문제 해결

* 🙀 chore: dockerfile FROM base image 경로를 직접적으로 입력하는 버전

* 🙀 chore: base-image outputs 타입을 oci로 변경

* 🙀 chore: docker 이미지 빌드 platform을 linux/amd64로 명시

* 🙀 chore: 타입 명시

* 🚨 !HOTFIX!: workflow on 설정 main push 일 경우로 수정

* ✨ feat: 서버 분할로 인해 cicd dev버전과 main 버전 분리 및 배포를 위한 설정 이분할

* 🙀 chore: 워크플로우 이름 변경

* 🙀 chore: 병합하면서 사라진 dev cicd의 on 조건 추가

* 🙀 chore: docker hub password token으로 변경

* 🙀 chore: client dockerfile 오타 수정

* 🙀 chore: dockerfile.test 오타 수정

* 🔨 refactor: cache type gha로 변경

* 🙀 chore: compose 배포 명령어 수정

* 🙀 chore: 두번 쓴 명령어 삭제

* 🙀 chore: env 설정 확인용 스크립트 추가

* 🙀 chore: cache docker layers 단계 없어도 동작하는지 확인

* 🙀 chore: 주석 제거

* 🔨 refactor: cache-from/to registry type으로 변경

* 🙀 chore: docker chche 타입 gha 타입으로 변경

* 🔨 refactor: github action deploy 과정 push에서만 사용되게 코드 추가

* 🔨 refactor: 브랜치별 워크플로우 시나리오 분리

* 🙀 chore: docker arg 선언 줄분리

* 🔨 refactor: docker compose 파일 type env 설정에 따른 port 설정해주는 코드 추가

* 🔨 refactor: HTTP/1.1 -> HTTP/2.0 개선 설정 추가

* 🔨 refactor: HTTP/2.0 -> HTTP/3.0 개선 설정 추가

* [HOTFIX] cicd 및 docker-compose 코드 수정 (#29)

* 🙀 chore: compose 코드 수정

* 🙀 chore: 현재 브랜치에서 deploy 체크하게 브랜치 설정코드 수정

* 🙀 chore: ssl_port 빈칸 표시

* 🙀 chore: # 처리 삭제

* 🙀 chore: 아예 SSL_PORT 설정 없애보기

* 🙀 chore: 우회방식 사용

* 🔨 refactor: docker compose 파일 test 버전 추가 및 cicd 코드 수정

* 🙀 chore: 합치기 전 설정 수정

* [#31] “Code 하이라이팅” 코드 품질 개선 (#36)

* 🔨 refactor: 변수 유틸함수로 분리

* 🔨 refactor: 애니메이션 코드 커스텀 훅으로 분리

* 🙀 chore: 훅에 주석 처리

* 🔨 refactor: 애니메이션 지속 시간 (매직넘버) 상수화

* 🙀 chore: 오타 수정

* 🔨 refactor: 클래스명 계산 로직 추상화

* 🔨 refactor: 마우스 호버 이벤트 훅 관리 + 라인클래스이름 추상화

* 🔨 refactor: 파싱 코드 추상화 + 함수 설명 주석 추가

* 🔨 refactor: Props Drilling이 나타나는 컴포넌트를 삭제하고 부모 컴포넌트로 이전.

* 🔨 refactor: viewer 클래스명 생성 함수 분리

* 🔨 refactor: 타입 분리 + index.ts로 import 문 간소화하여 가독성 개선

* [#26] 워크스페이스 페이지의 불필요한 리렌더링 개선 (#35)

* ✨ feat: css 속성 물음표 아이콘 좌표를 전역 상태로 관리하는 것이 아닌 local 상태 변수를 통해 관리하도록 변경

* Revert "✨ feat: css 속성 물음표 아이콘 좌표를 전역 상태로 관리하는 것이 아닌 local 상태 변수를 통해 관리하도록 변경"

This reverts commit b4409ef.

* 🔨 refactor: css속성 물음표 아이콘 좌표를 전역상태가 아닌 local 상태로 관리

* 🔨 refactor: 기존에 사용하던 매직넘버를 삭제하고 useLayoutEffect를 통해 툴팁의 높이를 직접 측정하고 툴팁의 좌표를 계산하는 방식으로 변경

* 🔨 refactor: css item 물음표 아이콘 위치를 전역 상태가 아닌 로컬 상태변수로 관리하도록 변경, 불필요한 cssTooltiptore 삭제

* 🔨 refactor: useCssTooltip 훅을 통해 좌표 계산을 하도록 변경

* 🙀 chore: 타입 단언 대신 타입 선언으로 변경경

* 🙀 chore: 불필요한 변수 및 반환값 삭제

* 🙀 chore: 커스텀 select에서 사용하는 type 및 enum type 폴더로 분리

* 🔨 refactor: css 클래스 리스트를 만드는 로직을 커스텀 훅으로 분리

* 🔨 refactor: useCssPropsStore에서 각 상태를 구조분해할당으로 사용하여 모든 상태를 구독하여 불필요한 리렌더링이 발생하는 문제를 해결함

* 🎨 style: 불필요한 스타일 코드 삭제

* 🙀 chore: 변경된 props 반영

* [#25] 사용자가이드 단계 이동시 헤더 내 모든 요소 리렌더링 개선 (#37)

* 🎨 style: 도움말버튼 컴포넌트 분리 (HelpButton)

* 🔨 refactor: 코치마크 단계 이동시 도움말버튼 리렌더링 개선

* 🔨 refactor: 헤더 상단 버튼 각각 메모이제이션

* 🔨 refactor: 코치마크 4단계로 이동하거나 4단계 였던 경우에만 HeaderButtons 리렌더링 되도록  메모이제이션

* 🔨 refactor: 워크스페이스 헤더 리렌더링 개선 완료

* 🐛 fix: 대소문자 오타 수정

* 📝 docs: README 수정

* [#34] 수동 저장 -> 자동 저장으로 변경 (#38)

* 🙀 chore: build시 오류 생기는 코드 제거

---------

Co-authored-by: YEONGJAE LEE <[email protected]>
Co-authored-by: Gyeungil Choi <[email protected]>
Co-authored-by: Yujin <[email protected]>
Co-authored-by: lee0jae330 <[email protected]>
Co-authored-by: chichoc <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
refactor 리팩토링
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[refactor] 워크스페이스 페이지 불필요한 리렌더링 개선
4 participants