TLDR
이번에는 Tab Navigation을 서비스를 개발하며 개선한 부분에 대해서 얘기해보려고 합니다. 그저 "탭의 상태" 에 따른 표시되는 UI가 변경되는 정도의 컴포넌트로 인식하고 있었지만, 사용자측면을 고려하고 베타테스트를 진행하면서 저 "상태"를 자연스럽게 관리하는 것에 대해 고민하고 개선한 내용과 그 로직에 대해 공유하려고 합니다.
초기 로직 : UI와 그 기능에 집중
처음 개발한 Tab Navigation은 UI와 Highligt Animation 등에 더 신경을 많이 썼습니다. 다른 페이지에서도 활용 될 수 있게끔, 탭의 갯수와 상관없이 UI를 적용할 수 있게끔 고민했고, 탭의 상태는 크게 신경쓰지 않았습니다.
컴포넌트의 Props를 통해 tab의 정보를 배열로 받았으며, tab navigation에서 선택된 탭의 상태를 useState를 통해 관리 했으며, Props를 통해 공유했습니다.
문제 1 : 활성화 된 탭의 상태가 계속해서 초기화 된다
useState로 활성화 된 탭을 관리하니 당연한 일이지만, 다른 페이지로 이동하거나 해당페이지 내 상세페이지로 들어갔다가 나올 때면 해당 탭이 초기화 되어있어서 사용자 불편함을 유발했습니다. 웹에서도 불편하겠지만, 웹뷰를 통해 앱으로 서비스를 지원하는 경우 그 불편함과 어색함이 더 부각되어 빠르게 개선이 필요했습니다.
그래서 URL params에 활성화된 tab을 기록하여 뒤로가기 stack에 남게끔 수정했습니다. URL params에 남아있다면 다른 어딘가로 이동했다가 와도 그 기록을 통해 기존에 활성화 되어 있던 탭으로 돌아 갈 수 있게끔 하여, 사용자의 불편함을 해결하였습니다.
import { useEffect } from 'react';
import { useSearchParams } from 'react-router-dom';
function TabNavigation() {
const [ query, setQuery ] = useSearchParams();
useEffect(() => {
query.set('tab', tab상태);
setQuery(query);
}, [tab 상태])
...
}
문제 2 : 뒤로가기 stack이 너무 많이 쌓임
위 방법을 통해 해결한건 사용자 입장에서 다른 불편함을 유발했습니다. 탭이 변경될 때 마다 뒤로가기 stack이 쌓이게 된 것 입니다. 뒤로가기를 눌러도 이전 탭, 화면으로 돌아가는게 아닌 tab navigation 화면그대로 URL 만 되돌아 가는 현상이 발생했고 URL이 보이지 않는 앱 입장에서는 더욱 치명적으로 다가왔습니다.
위 사진은 쌓여있는 뒤로가기 stack의 예시입니다. 사용자 입장에서 tab Navigation을 다룬후 pageC로 이동했다가 다시 뒤로가기를 통해 pageA로 가려면? pageB?tab=3 -> tab=2 -> tab=1 -> pageB 라는 과정을 통해 pageB에 뒤로가기 4번 시도하는 동안 갖혀있게 됩니다.
문제 재 정의
URL params를 활용하는 방식의 한계를 알았으니 다른 방식으로의 수정이 필요했습니다. 기존 useState방식은 상태가 유지되지 못하니, 전역상태와 같은 방법을 고민하기 이전에 해결해야하는 문제에 대해 구체적으로 정의가 필요했습니다.
- tab navigation page로 정상적으로 이동했다면? "탭의 상태가 초기화" 되야 한다.
- 다른 페이지 / 심지어 외부페이지 이동했다가 "뒤로가기"를 해서 페이지로 돌아온다면 "탭의 상태 유지"가 되야한다
- 새로고침 시, 탭 유지
LocalStorge
서비스 내부에는 아예 외부 다른 페이지로 이동하는 링크가 있습니다. 이런 부분까지 고려한다면, 상태 관리는 localStorage에서 하는 게 맞다고 판단했습니다.
뒤로가기 상태 구분하기
tab navigation이 있는 페이지에서 위 문제를 해결하기 위해서는? 페이지를 접근할때 뒤로가기로 접근했는지, 아니면 페이지로 새롭게 접근했는지를 구분할 수 있어야 합니다.
- window.addEventListener('popstate') : 뒤로가기 할 때 실행
- useNavigationType(): react-router-dom 내 hooks로 어떤 동작을 통해 페이지로 접근했는지 확인
해당 페이지에 어떤 동작으로 접근했는지 확인하기 위해 window.history.state를 사용하려고 한적이 있으나, React SPA 때문인지 예상대로 동작하지 않았습니다. performance.navigation.type 도 마찬가지로 원하는데로 분류할 수 없어서 위 두가지 방법을 엮어서 뒤로가기 상태를 구분했습니다.
코드
import React, { useState, useEffect } from 'react';
import { useLocation, useNavigationType } from 'react-router-dom';
const TabContent = ({ tab }) => (
<div>
<h2>{tab} 페이지 내용</h2>
</div>
);
const TabNavigation = () => {
const isValidTab = (tab) => ['tab1', 'tab2', 'tab3'].includes(tab);
// 현재 네비게이션 타입과 위치를 가져옵니다.
const navigationType = useNavigationType();
const location = useLocation();
const initialTab =
navigationType === 'POP'
? localStorage.getItem('activeTab') || 'tab1' // 뒤로가기인 경우 저장된 탭 유지
: 'tab1'; // 새로고침 또는 직접 접근 시 기본값 설정
const [activeTab, setActiveTab] = useState(
isValidTab(initialTab) ? initialTab : 'tab1'
);
useEffect(() => {
if (navigationType !== 'POP') {
setActiveTab('tab1');
localStorage.removeItem('activeTab');
}
}, []);
useEffect(() => {
const restoredTab = localStorage.getItem('activeTab');
if (navigationType === 'POP' && isValidTab(restoredTab)) {
setActiveTab(restoredTab);
}
const handleTabChange = (tab) => {
setActiveTab(tab);
localStorage.setItem('activeTab', tab);
};
return () => {
handleTabChange(activeTab); // 컴포넌트 언마운트 시 현재 탭 저장
};
}, [location, navigationType]);
const handleTabClick = (tab) => {
setActiveTab(tab);
localStorage.setItem('activeTab', tab); // 탭 상태를 localStorage에 저장
};
return (
<div>
<div>
<button
onClick={() => handleTabClick('tab1')}
className={activeTab === 'tab1' ? 'active' : ''}
>
탭 1
</button>
<button
onClick={() => handleTabClick('tab2')}
className={activeTab === 'tab2' ? 'active' : ''}
>
탭 2
</button>
<button
onClick={() => handleTabClick('tab3')}
className={activeTab === 'tab3' ? 'active' : ''}
>
탭 3
</button>
</div>
<div>
<TabContent tab={activeTab} />
</div>
</div>
);
};
const App = () => (
<div>
<h1>탭 네비게이션 예제</h1>
<TabNavigation />
</div>
);
export default App;
마무리
지금까지 TabNavigation 의 상태유지를 위해 개선한 내용에 대해 소개해 드렸습니다. 이번 문제를 해결하기 위해 React에서 페이지 상태를 분류할 수 있는 다양한 이벤트타입과 hooks, 로직 등에 대해서 새롭게 알게된 부분이 많았던 것 같습니다.
'React' 카테고리의 다른 글
[React] 비즈니스 / 도메인 로직을 통해 코드 응집도 높이기 (3) | 2024.12.03 |
---|---|
[React] 웹뷰로 연결된 Android Stuiod / IOS 에 데이터 주고받기 ( 브릿지 통신 ) (0) | 2024.11.11 |
[AWS] Sub Domain 연결하기 (5) | 2024.10.31 |
[React + Vite + Nginx + Docker + AWS] 를 활용하여 배포 해봅시다! (0) | 2024.06.05 |
[React, Typescript] Intersection Observer를 활용하여, 사용자 화면에 맞게 변화하는 컴포넌트 만들기 (1) | 2024.05.14 |