코로 넘어져도 헤딩만 하면 그만
[MD]처음 페이지에 진입하면 5초 뒤 사라지는 버블 컴포넌트(feat. localStorage) 본문
프로젝트가 진행되면서 다음과 같이 생긴 버블 공통 컴포넌트를 구현할 일이 생겼다.
일단은... 공통 컴포넌트이기 때문에, 해당 버블은 여러 페이지에서 쓰일 것이다. 그러니 최대한 다른 곳에 재사용 가능하도록 생각하며 구현하기로 하였다.
interface BubbleProps {
text: string;
type: "topLeft" | "topRight" | "bottomLeft" | "bottomRight";
//props를 적극 활용하여 재사용성을 높임.
}
const Bubble = ({ text, type }: BubbleProps) => {
return (
<div className={classNames(styles.bubbleWrapper, styles[type])}>{text}</div>
);
};
export default Bubble;
props로 text(버블 내부에 들어갈 텍스트 내용)와 type(말꼬리의 위치)를 받는다. 말꼬리 부분은 코드 내에서 큰 의미가 없어 html 태그를 달지 않고 CSS에서 가상 선택자 ::after 만 사용하여 구현하였다.
/* NOTE: 공통 꼬리 스타일 */
.bubbleWrapper::after {
content: "";
position: absolute;
width: 0;
height: 0;
}
/* NOTE: 꼬리 위치별 type */
.topRight::after {
bottom: 100%;
right: 0.5rem;
border-bottom: 8.212px solid rgba(0, 0, 0, 0.6);
border-left: 13px solid transparent;
}
.bottomRight::after {
top: 100%;
right: 0.5rem;
border-top: 8.212px solid rgba(0, 0, 0, 0.6);
border-left: 13px solid transparent;
}
.topLeft::after {
bottom: 100%;
left: 0.5rem;
border-bottom: 8.212px solid rgba(0, 0, 0, 0.6);
border-right: 13px solid transparent;
}
.bottomLeft::after {
top: 100%;
left: 0.5rem;
border-top: 8.212px solid rgba(0, 0, 0, 0.6);
border-right: 13px solid transparent;
}
🚩기능에 대한 조건?
이제 기능 구현으로 넘어가보자.
기획 팀에서 전달한 해당 컴포넌트의 조건은 두 가지로
사용자가 접속했을 때 5초 동안 화면에 유지되다 사라지며,
이후 사용자가 같은 페이지에 다시 접속하면 재등장하지 않는다.
그렇다면 사용자가 해당 페이지에 접속 했는지 기록을 유지하면서, 버블이 등장한 뒤 5초가 지나면 setTimeout으로 처리하면 된다는 구조를 파악할 수 있다. 흔히 '일주일 or 하루 동안 보이지 않기' 등 팝업의 기능을 처리할 때 클라이언트의 스토리지를 사용하는데, 이 경우에도 유사하게 처리하면 될 것 같았다.
자주 쓰이는 클라이언트 측 저장소에는 크게 두 종류가 있다.
이중 로컬 스토리지와 세션 스토리지의 중요한 차이점은 아래와 같다.
1) 로컬 스토리지의 경우, 브라우저가 종료되어도 계속 유지된다. 세션 스토리지는 탭이나 창이 닫히면 모두 삭제된다.
2) 로컬 스토리지는 탭을 여러 개 열어도 그 사이에 공유된다. 세션 스토리지는 서로 공유되지 않는다.
현재 조건은 사용자가 처음 해당 서비스를 사용할 때만 버블이 보이도록 하는 것이다. 따라서 사용자가 해당 페이지에 이전에 접속한 적이 있는지 파악해야 한다. 따라서 로컬 스토리지를 쓰는 쪽이 본 기능에 맞다고 생각 되었다. 세션 스토리지의 경우 브라우저가 닫히면 아예 사라지기 때문에 매번 사용자가 접속 시 등장할 문제가 있다.
또한 버블을 사용하는 페이지에서 useState와 useEffect를 사용하기보다, 버블 컴포넌트 자체에서 props로 받아 사용하는 것이 보다 재사용성이 높고 코드가 깔끔해질 것 같아 아래와 같이 작성하였다.
버블 show를 useState로 처리하면서, useEffect를 사용하여 localStorage 저장 및 보여지는 시간을 적용한다.
//bubble 컴포넌트
interface BubbleProps {
text: string;
type: "topLeft" | "topRight" | "bottomLeft" | "bottomRight";
seconds: number;
localStorageKey: string;
//seconds로 몇 초간 유지될 지 받는다.
//localStorageKey는 해당 버블이 쓰이는 페이지에서 어떤 key로 저장할 지 결정한다.
}
const Bubble = ({ text, type, seconds, localStorageKey }: BubbleProps) => {
const [showBubble, setShowBubble] = useState(false);
//bubble이 보여지는 것을 결정한다.
useEffect(() => {
const hasShown = localStorage.getItem(localStorageKey);
if (!hasShown) {
setShowBubble(true);
//setTimeout 함수를 사용하여 props로 받은 시간만큼 유지하다 제거한다.
const timer = setTimeout(() => {
setShowBubble(false);
localStorage.setItem(localStorageKey, "true");
}, seconds * 1000);
return () => clearTimeout(timer);
//타이머 제거
}
}, [seconds, localStorageKey]);
if (!showBubble) return null;
return (
<div className={classNames(styles.bubbleWrapper, styles[type])}>{text}</div>
);
};
export default Bubble;
🤔 개선 방향?
1) 나중에는 사용자가 서비스에 접속할 때마다 계속 등장하도록 하는 조건이 생길 수 있을 것 같다. 아니면 일주일 뒤에 다시 등장하게 하거나... 그때는 컴포넌트를 리펙토링하여 seconds props를 수정하도록 하겠다.
2) 로컬 스토리지를 사용하는 식으로 작성해 두었는데, 다시 생각해보니 버블이 여러 페이지에 반복적으로 사용되면 관련된 스토리지가 로컬에 계속 생겨나고 유지되는 문제가 발생할 것 같다. 현재로서는 쓰이는 곳이 한 페이지 뿐이라 상관은 없겠지만... 프로젝트를 진행하며 좋은 리펙토링 방식을 발견하면 개선할 생각이다.
'Project' 카테고리의 다른 글
[MD]Next의 SSR과 Quill의 module 충돌 문제 (500 Server Error) (0) | 2025.02.03 |
---|---|
[MD]ReactQuill register로 텍스트 글자 수 제한(MaxLength) (0) | 2025.01.08 |
[MD]Quill의 placeholder에서 한글 IME 인식 문제: 기능 구현 (0) | 2025.01.07 |
[MD]React-quill의 칼라 팔레트 개수 변경 (0) | 2024.12.30 |
[MD]Next에서 useSearchParams 사용(Suspense) (1) | 2024.09.13 |