본문 바로가기
2024/TIL

[React] 로컬 스토리지를 사용한 데이터 캐싱

by ye-jji 2024. 7. 1.

문제

  1. 개요
    • 상품 리스트 페이지에서 상품을 클릭해 상세 페이지로 이동 후, 뒤로가기 버튼을 눌렀을 때 페이지가 새로고침되면서 리스트의 상단으로 이동하기 때문에 UX가 떨어진다는 피드백을 받음.
    • 페이지에 카테고리와 필터가 중복으로 적용되고 있고 상품 데이터의 양이 많아 서버로부터 많은 리소스를 받아와야 함.
  2. 위치값 사용의 문제
    • 화면의 높이값을 저장했다가 불러와서 로드해주는 방법을 사용했지만 새로고침이 되고 그 위치까지 자동으로 스크롤 되는 액션이 발생함
    • 위치까지 빠르게 이동하면서 그 사이의 데이터를 빠르게 받아와 보여주는 과정에서 많은 리소스가 낭비됨
  3. 기획자의 요구사항
    • 새로고침 없이 이전 리스트 화면을 보여주기를 원함.
    • 현재는 페이지 위치값을 넘겨서 스크롤하는 방식으로 구현됨.
    • 런칭이 임박했고, 런칭 후 이벤트 때문에 유저가 본 상품의 상태가 정확하게 반영될 필요가 있음.
  4. 향후 계획
    • 런칭 후 이슈 해결을 위해 캐싱 전략이나 데이터 동기화 방식 등 다양한 기술적 해결책을 고민할 예정.

해결방법

캐싱된 데이터를 기반으로 페이지를 먼저 로드하고, 백그라운드에서 실시간 데이터를 동기화하는 방법

1. 데이터 요청 및 캐싱

먼저, 상품 리스트 페이지 컴포넌트를 작성
이 컴포넌트는 서버에서 데이터를 받아오고, 로컬 스토리지에 저장한 후, 데이터를 렌더링.

2. 캐싱된 데이터로 페이지 로드 및 실시간 데이터 동기화

캐싱된 데이터가 있으면 먼저 이를 사용해 페이지를 렌더링, 백그라운드에서 실시간 데이터를 요청하여 데이터를 갱신.

3. 오류 처리 및 데이터 유효성 검사

서버 요청이 실패하면 캐싱된 데이터를 사용, 캐시된 데이터의 유효성을 주기적으로 검사하여 오래된 데이터를 제거.

import React, { useEffect, useState } from 'react';

const ProductList = () => {
  const [products, setProducts] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // 로컬 스토리지에서 캐싱된 데이터 불러오기
    const cachedData = localStorage.getItem('productList');
    if (cachedData) {
      setProducts(JSON.parse(cachedData));
      setLoading(false);
    }

    // 서버에서 실시간 데이터 요청
    fetch('/api/products')
      .then(response => response.json())
      .then(data => {
        setProducts(data);
        setLoading(false);
        localStorage.setItem('productList', JSON.stringify(data));
        localStorage.setItem('cacheTime', new Date().getTime());
      })
      .catch(error => {
        console.error('데이터를 불러오는데 실패했습니다.', error);
        setLoading(false);
      });
  }, []);

  // 캐시 유효성 검사
  useEffect(() => {
    const cacheTime = localStorage.getItem('cacheTime');
    const now = new Date().getTime();
    const CACHE_VALIDITY_DURATION = 1000 * 60 * 60 * 24; // 1일

    if (cacheTime && now - cacheTime > CACHE_VALIDITY_DURATION) {
      localStorage.removeItem('productList');
      localStorage.removeItem('cacheTime');
    }
  }, []);

  if (loading) {
    return <div>Loading...</div>;
  }

  return (
    <div id="product-list">
      {products.map(product => (
        <div className="product-item" key={product.id}>
          <h2>{product.name}</h2>
          <p>{product.description}</p>
          <span>{product.price}</span>
        </div>
      ))}
    </div>
  );
};

export default ProductList;