300x250
Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- eslint 자동화
- 크로마틱
- webpack
- 클로저
- 성능 베이스캠프
- eslint에러 자동fix
- vscode
- 클린코드
- CSS
- 우테코
- storybook
- Husky
- import정리
- 자바스크립트
- importOrder
- string-width
- 프로젝트
- 우아한테크코스
- prettier 자동화
- 프로젝트 카페인
- git hooks
- JavaScript
- IDL attributes
- css instead of js
- 협업
- 유틸함수
- 카페인
- chromatic
- 이슈번호자동화
- react
Archives
- Today
- Total
FEB:)DAIN
[refactor] Ref를 이용해 만든 팝업 메뉴 CSS만으로 구현하기 본문
728x90
0. 리팩터링을 진행한 이유와 요구사항
팝업 메뉴를 구현한 팀원의 코드를 보니 ref를 쓰지 않고, css만으로도 간단하게 구현할 수 있을 것 같아 리팩터링을 진행했다.
팀원의 요구사항은 다음과 같았다.
1) 트리거의 위치가 바뀌어도 팝업 메뉴가 그 트리거 옆에 있어야 한다.
1-1) 얼마나 떨어져 있을지도 정할 수 있어야 한다.
2) 팝업 메뉴 크기는 자식 메뉴에 맞춰 유동적으로 바뀌어야 한다.
2-1) 자식 메뉴 수는 줄이거나 늘릴 수 있다. 메뉴 수에 따라 팝업 메뉴 높이가 바뀌어야 한다.
2-2) 자식 메뉴 글자 수가 길어지면 그 길이에 맞춰 팝업 메뉴 너비가 늘어나야 한다.
1. 코드 비교
1) Ref를 이용한 팝업 메뉴 - 리팩터링 전
// Menu.tsx
<PopupMenu trigger={<UserCircleIcon width="2.8rem" stroke="#333" />} menus={loginMenus} />
// PopupMenu.tsx
import type { PropsWithChildren, ReactNode } from 'react';
import { useEffect, useRef, useState } from 'react';
import Box from '@common/Box';
import FlexBox from '@common/FlexBox';
import Menus from './Menus';
interface Props {
trigger: ReactNode;
menus: PropsWithChildren<{ onClick: () => void }>[];
}
const PopupMenu = ({ trigger, menus }: Props) => {
const triggerRef = useRef<HTMLButtonElement>(null);
const [isOpen, setIsOpen] = useState(false);
const [triggerWidth, setTriggerWidth] = useState(0);
const handleToggleMenu = () => {
setIsOpen((prev) => !prev);
};
const handleCloseMenu = () => {
setIsOpen(false);
};
useEffect(() => {
if (triggerRef.current) setTriggerWidth(triggerRef.current.offsetWidth);
}, []);
return (
<FlexBox css={container}>
<button ref={triggerRef} onClick={handleToggleMenu}>
{trigger}
</button>
<Box css={getMenuContainerCss(triggerWidth)}>
{isOpen && <Menus menus={menus} closeMenu={handleCloseMenu} />}
</Box>
</FlexBox>
);
};
const container = css`
position: relative;
`;
const getMenuContainerCss = (triggerWidth: number) => {
return css`
position: absolute;
top: -2rem;
left: calc(${triggerWidth}px + 2rem);
`;
};
2) CSS로 만든 팝업 메뉴 - 리팩터링 후
// Menu.tsx
<PopupMenu menus={loginMenus} />
// PopupMenu.tsx
import { UserCircleIcon } from '@heroicons/react/24/outline';
import { css } from 'styled-components';
import type { PropsWithChildren } from 'react';
import { useState } from 'react';
import FlexBox from '@common/FlexBox';
import Menus from './Menus';
interface Props {
menus: PropsWithChildren<{ onClick: () => void }>[];
}
const PopupMenu = ({ menus }: Props) => {
const [isOpen, setIsOpen] = useState(false);
const handleToggleMenu = () => {
setIsOpen((prev) => !prev);
};
const handleCloseMenu = () => {
setIsOpen(false);
};
return (
<FlexBox css={container}>
{isOpen && <Menus menus={menus} closeMenu={handleCloseMenu} />}
<button onClick={handleToggleMenu}>
<UserCircleIcon width="2.8rem" stroke="#333" />
</button>
</FlexBox>
);
};
const container = css`
position: relative;
display: inline-block;
& > ul:first-child {
position: absolute;
top: -18px;
left: calc(100% + 20px);
}
`;
2. 성능 비교 (with 크롬 브라우저의 performance, lighthouse)
✨ 결과
코드가 간결해져 가독성이 훨씬 좋아졌다!😄 불필요한 Ref, 상태관리도 사라지고 디자인을 위한 코드는 CSS에만 둘 수 있게 됐다.
짧은 시간 내에 성능도 개선되었다.
리팩터링하면서 반응형 디자인을 위해 미디어 쿼리도 적용했는데, 성능이 나빠지기는 커녕 더 좋아졌다.
1. TBT (Total Blocking Time) 38.3% 감소
TBT란? FCP(First Content Paint, 화면에 콘텐츠가 처음 그려지는 시간)와 TTI(상호작용 시작 시간) 사이의 총 시간
2. Lighthouse 총 점수 10점 증가
3. 레이아웃 재계산 시간 50% 이상 감소
투자한 시간(30분 정도)에 비해 훨씬 더 좋은 결과를 얻었다.
728x90
'기록 > 프로젝트 카페인' 카테고리의 다른 글
[refactor] 간단하게 사용자 경험을 개선해보자 (0) | 2023.11.08 |
---|---|
프로젝트 카페인에서의 에러 처리 (0) | 2023.11.03 |
husky로 prettier, eslint 적용 자동화하기 (0) | 2023.07.19 |
import를 깔끔하게 정리하고 싶어 (0) | 2023.07.15 |
[카페인] 전기차 충전소 지도 서비스 프로젝트 시작 (6/27~) (2) | 2023.06.29 |