저번 시간에 무한 스크롤을 구현했는데, 무한 스크롤을 통해 가져온 사진을 보다가 클릭해 자세히 본 뒤, 뒤로 가기를 통해 돌아오니 초기 화면으로 돌아오는게 매우 불편하다고 느꼈다. 그래서 바로 고치기로 했다. 스크롤 시 스크롤 정보를 어딘가에 저장을 해 두어야 할 것 같은데, 로컬 스토리지는 사라지지 않기 때문에 적절하지 않다고 생각했다. 그렇게 세션 스토리지 보게 되었고, 유효 기간이 마음에 들어 선택하게 되었다.
현재 떠있는 탭 내에서만 유지된다. 페이지를 새로 고침할 때 sessionStorage에 저장된 데이터는 사라지지 않는다. 하지만 탭을 닫고 새로 열 때는 사라진다.
각 도메인별로 약 5MB 정도의 저장 용량이 있다.
sessionStorage.setItem(key, value)
sessionStorage.getItem(key)
sessionStorage.remove(key)
sessionStorage.clear()
sessionStorage.setItem('user', JSON.stringify({ name: 'John', age: 30 }));
const user = JSON.parse(sessionStorage.getItem('user'));
console.log(user.name); // John
sessionStorage.removeItem('user');
sessionStorage.clear();
sessionStorage
에 저장된 값들을 구분하기 위한 string 값 (ex: explore)유저가 스크롤을 할 시 위치를 유지해야하는 컴포넌트의 scrollTop
을 저장한다.
useEffect(() => {
const container = containerRef.current;
if (!container) return;
const handleScroll = () => {
const { scrollTop, scrollHeight } = container;
sessionStorage.setItem(`${key}-scroll`, `${scrollTop}`);
};
// 스크롤 이벤트 리스너 등록
container.addEventListener('scroll', handleScroll);
// 컴포넌트 언마운트 시에 이벤트 리스너 제거
return () => {
container.removeEventListener('scroll', handleScroll);
};
}, []);
페이지 로드 시 세션 스토리지의 item 값이 있는 경우 기존 데이터를 업데이트 해준다.
useEffect(() => {
if (sessionStorage.getItem(`${key}-item`) && setList)
setList(JSON.parse(sessionStorage.getItem(`${key}-item`)!));
}, []);
기존 데이터가 업데이트 되고 난 뒤 스크롤을 저장 되어있는 스크롤 위치로 이동시킨다.
useEffectAfterMount(() => {
const container = containerRef.current;
if (!container) return;
const memoScroll = sessionStorage.getItem(`${key}-scroll`);
container.scrollTo({
top: memoScroll,
});
}, [list]);
에러발생 - 기존 데이터를 불러오기 전 스크롤을 먼저 해버리는 이슈
list가 정상적으로 가져와 졌지만,
0 페이지의 맨 아래 데이터
로만 스크롤이 이동하는 이슈가 생겼다. 디버깅을 통해 이유를 알아보니 데이터 불러오기 전 이미 이동을 해버리는 상황이였다. 그래서 나는 스크롤 위치를 저장할 때컴포넌트의 높이
도 같이 저장 해 해당 높이보다 작은 경우 스크롤을 하지 않기로 했다.//스크롤 위치 저장 시 sessionStorage.setItem(`${key}-component-height`, `${scrollHeight}`); //스크롤 위치 이동 중 const memoScroll = sessionStorage.getItem(`${key}-scroll`); const memeoScrollHeight = sessionStorage.getItem(`${key}-component-height`); // 컴포넌트 위치를 비교 해 기존보다 작으면 스크롤 하지 않음 if (memeoScrollHeight && container.scrollHeight < memeoScrollHeight) return;
해당 key값과 관련된 모든 sessionStorage 값들을 삭제한다.
const reset = async () => {
sessionStorage.removeItem(`${key}-scroll`);
sessionStorage.removeItem(`${key}-component-height`);
sessionStorage.removeItem(`${key}-item`);
sessionStorage.removeItem(`${key}-page`);
};
sessionStorage
에 값이 있다면, 초기값을 해당 값으로 세팅해준다.
또한 데이터 저장을 위해 key 값을 useRememberScroll
과 같게 세팅해준다.
const {
data: OOTDData,
isLoading: OOTDIsLoading,
containerRef: OOTDRef,
hasNextPage: OOTDHasNextPage,
reset: ootdReset,
} = useInfiniteScroll({
fetchDataFunction: fetchOOTDDataFunction,
size: 12,
initialData: sessionStorage.getItem('explore-item')
? JSON.parse(sessionStorage.getItem('explore-item')!)
: [],
initialPage: sessionStorage.getItem('explore-page')
? Number(sessionStorage.getItem('explore-page'))
: 0,
key: 'explore',
});
데이터를 불러올 때 마다, 데이터를 key-item
으로 저장한다.
useEffectAfterMount(() => {
if (data.length === 0) return;
sessionStorage.setItem(`${key}-item`, JSON.stringify(data));
sessionStorage.setItem(`${key}-page`, String(page));
}, [data]);
const [OOTDList, setOOTDList] = useState<OOTDListType[]>([]);
useRememberScroll({
key: 'explore',
containerRef: OOTDRef,
setList: setOOTDList,
list: OOTDList,
});
const {
data: OOTDData,
isLoading: OOTDIsLoading,
containerRef: OOTDRef,
hasNextPage: OOTDHasNextPage,
reset: ootdReset,
} = useInfiniteScroll({
fetchDataFunction: fetchOOTDDataFunction,
size: 12,
initialData: [],
initialPage: 0,
});
return (
<S.ClothList ref={OOTDRef}>
<ImageList
onClick={onClickImageList}
data={OOTDList.map((item) => {
return { ootdId: item.id, ootdImage: item.imageUrl };
})}
type={'column'}
/>
{OOTDIsLoading && OOTDHasNextPage && <Spinner />}
</S.ClothList>
)
아이템을 임시로 담고있다보니, 해당 아이템 리스트에 삭제나 추가의 변화가 생겼을 때 대응이 필요했다.
아이템 등록, 삭제 로직에 reset() 함수를 통해 초기화를 진행했다.
const { reset } = useRememberScroll({ key: `mypage-${myId}-cloth` });
const onClickSubmitButton = async () => {
//옷 등록 api
const payload = {
...
};
const result = await postCloth(payload);
reset();
if (result) router.push(`/mypage/${myId}`);
};
이 경우에 남의 리스트에 삭제나 추가가 된 시점을 알 수 없다 보니, reset()
함수를 섣불리 사용할 수 없었다.
이 내용에 대해 회의를 진행해 나온 결론은, Date()
함수를 통해 시간을 함께 sessionStorage
에 저장한 뒤 현재 시간과 비교해 일정 시간 이상이 지나면 초기화 하는 방법을 사용해 보기로 했다.
해당 기능이 구현되면 추가로 포스팅 하겠다.
데이터와 스크롤위치가 성공적으로 유지되니 서비스가 훨씬 깔끔해졌다! 하나하나 완성되어가는 모습을 보니 뿌듯하다
완성된 화면이 궁금하다면 https://apps.apple.com/kr/app/ootdzip/id6499494035 해당 어플에서 확인할 수 있다!