[TIL] 20250418

김민석·2025년 4월 18일
post-thumbnail

오늘 목표

  • 수업 전 7시 기상 후 운동(O)
  • 수업 내용 모르는 거 및 공부내용 정리(O)
  • JS 강의 듣기(O)

공부내용

디바운싱(Debouncing)

디바운싱은 연속적으로 발생하는 이벤트들 중 마지막 이벤트만 처리하는 기법입니다. 일정 시간 내에 추가 이벤트가 발생하면 타이머를 재설정하고, 더 이상 이벤트가 발생하지 않을 때 최종적으로 함수를 실행합니다.

사용 사례

  • 검색창의 자동완성 기능 (사용자가 타이핑을 멈춘 후에만 API 호출)
  • 윈도우 리사이즈 이벤트 처리
  • 폼 입력값 검증

예시 코드

function debounce(func, delay) {
  let timeoutId;

  return function(...args) {
// 이전 타이머가 있으면 취소
    if (timeoutId) {
      clearTimeout(timeoutId);
    }

// 새로운 타이머 설정
    timeoutId = setTimeout(() => {
      func.apply(this, args);
    }, delay);
  };
}

// 사용 예시: 검색창 입력 디바운싱
const searchInput = document.getElementById('search');
const handleSearch = (e) => {
  console.log('Searching for:', e.target.value);
// API 호출 등의 작업
};

// 300ms 동안 추가 입력이 없을 때만 검색 수행
searchInput.addEventListener('input', debounce(handleSearch, 300));

스로틀링(Throttling)

스로틀링은 일정 시간 간격으로 함수를 최대 한 번만 실행하도록 제한하는 기법입니다. 디바운싱과 달리 마지막 이벤트를 기다리지 않고, 정해진 주기마다 한 번씩 함수를 실행합니다.

사용 사례

  • 스크롤 이벤트 처리 (무한 스크롤)
  • 게임의 키보드/마우스 이벤트 처리
  • 빠르게 변화하는 UI 업데이트 (차트, 그래프 등)

예시 코드

function throttle(func, limit) {
  let inThrottle = false;

  return function(...args) {
    if (!inThrottle) {
      func.apply(this, args);
      inThrottle = true;

// 지정된 시간 후에 플래그 초기화
      setTimeout(() => {
        inThrottle = false;
      }, limit);
    }
  };
}

// 사용 예시: 스크롤 이벤트 스로틀링
const handleScroll = () => {
  console.log('Scroll position:', window.scrollY);
// 스크롤 위치 기반 작업 수행
};

// 200ms마다 최대 한 번씩만 스크롤 이벤트 처리
window.addEventListener('scroll', throttle(handleScroll, 200));

차이점 요약

  • 디바운싱: 마지막 이벤트 후 일정 시간이 지나면 한 번만 실행 (예: 타이핑이 끝난 후)
  • 스로틀링: 일정 시간 간격으로 최대 한 번씩 실행 (예: 200ms마다 한 번씩)

실무에서는?

실제 프로젝트에서는 Lodash나 Underscore 같은 라이브러리의 구현체를 많이 사용합니다:

import _ from 'lodash';

// 디바운싱
const debouncedFunction = _.debounce(originalFunction, 300);

// 스로틀링
const throttledFunction = _.throttle(originalFunction, 200);

이 두 기법을 적절히 활용하면 성능을 크게 개선하고 불필요한 API 호출과 계산을 줄일 수 있습니다.

프록시 서버 설정

프론트와 백엔드의 주소가 다르면 CORS 오류가 뜨기 때문에 이를 해결하고 API를 효율적으로 관리하기 위하여 설정한다. 또한 API 경로를 단순화 할 수 있따.

server: {
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true,
        rewrite: path => path.replace(/^\/api/, ''),
      },
    },
  },

위에 코드는 현재 json-server 주소가 localhost:3000 이기 떄문에 이렇게 설정하였고
사용하려면 /api를 쓰면 'http://localhost:3000' 를 사용한 주소로 가게된다. 참 편리하니 프로젝트 설정할 때 꼭 해줘야겠다.

isActive를 처음부터 true로 설정하면 애니메이션이 안 되는 이유??

 const [isActive, setIsActive] = useState(false)
  const navigate = useNavigate()
  // 마운트 직 후 active 클래스를 추가해야함 그래야 애니메이션 적용(?)
  useEffect(() => {
    const timer = setTimeout(() => {
      setIsActive(true)
      document.body.style.overflow = 'hidden'
    }, 1000)

    return () => {
      clearTimeout(timer)
      document.body.style.overflow = 'auto'
    }
  }, [])
  const handleClose = () => {
    setIsActive(false)
    setTimeout(onClose, 300)
  }

애니메이션은 변화가 있어야 작동함.

처음에는 isActive를 true로 두는 게 맞다고 생각했다.
Modal이 나타날 때 .active 클래스를 부여해서 애니메이션 효과를 주려는 의도였다.
하지만 React에서는 컴포넌트가 마운트되면서 곧바로 .active 클래스가 붙기 때문에,
CSS는 이미 애니메이션이 적용된 상태라고 인식해 트랜지션이 실행되지 않는다.
그래서 useEffect를 사용해 렌더링 이후 일정 시간 후에 setIsActive(true)를 호출하면,상태의 변화가 발생하고 그에 따라 CSS 트랜지션이 작동하면서 애니메이션이 실행된다.

json-server 에 POST 및 PUT

오늘은 detail 페이지에서 해당 상품을 cart라는 json에 넣는 것을 하였다.
product의 정보를 가져오고 detail 페이지에 count를 정하여 그 정보를 modal에 보내주어 modal 에서 addToCart를 하면 cart정보와 count를 합쳐 새로운 Item을 만들어 api쪽으로 보내주었다.

export const addCartData = async cartItem => {
  try {
    const cart = await getCartData()
    const existingItem = cart.find(item => item.id === cartItem.id)
    if (existingItem) {
      const updateItem = {
        ...existingItem,
        count: existingItem.count + cartItem.count,
      }
      const response = await axios.put(`/api/cart/${existingItem.id}`, updateItem)
      return response.data
    } else {
      const response = await axios.post(`/api/cart/`, cartItem)
      return response.data
    }
  } catch (error) {
    console.log('getCartData', error)
    throw error
  }
}

해당 코드는 cartItem이라는 상품 정보를 받아 장바구니 데이터를 서버에 저장하는 함수이다.
1. getCartData()를 통해 현재 장바구니(cart)에 있는 모든 데이터를 불러온다.
2. 불러온 데이터 중, cartItem과 같은 id를 가진 항목이 있는지 find()로 확인하고, 있으면 existingItem에 저장한다.
3. 만약 existingItem이 존재한다면:

  • 해당 아이템을 수정할 준비를 한다.
  • ...existingItem을 통해 기존 데이터를 유지하고,
  • countcartItem.count 만큼 더한 새로운 객체인 updateItem을 만든다.
    → 이는 불변성을 유지하면서 상태를 갱신하기 위해 사용하는 패턴이다.
  • 이후 axios.put()을 통해 해당 아이템의 정보를 서버에 업데이트 요청한다.
  1. 반대로 existingItem이 존재하지 않는 경우(즉, 장바구니에 없는 상품이라면):

    • axios.post()를 통해 새 상품을 장바구니에 추가한다.
  2. 이 모든 과정에서 오류가 발생하면 catch 블록에서 로그를 찍고, 에러를 다시 던진다(throw) → 상위에서 처리 가능하게 하기 위함이다.

API 호출 로직을 함수로 분리하면 좋은 점

  1. 컴포넌트는 UI에 집중
  • 컴포넌트에서 API호출 도 하게 된다면 코드가 너무 복잡하고 가독성이 떨어짐 또한 API 함수를 따로 분리하면 재사용성과 유지보수가 좋아짐
  1. 테스트가 쉬움
  • 테스트를 진행할 때 따로 분리하면 테스트가 쉬워짐
profile
나만의 기록장

0개의 댓글