디바운싱은 연속적으로 발생하는 이벤트들 중 마지막 이벤트만 처리하는 기법입니다. 일정 시간 내에 추가 이벤트가 발생하면 타이머를 재설정하고, 더 이상 이벤트가 발생하지 않을 때 최종적으로 함수를 실행합니다.
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));
스로틀링은 일정 시간 간격으로 함수를 최대 한 번만 실행하도록 제한하는 기법입니다. 디바운싱과 달리 마지막 이벤트를 기다리지 않고, 정해진 주기마다 한 번씩 함수를 실행합니다.
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));
실제 프로젝트에서는 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' 를 사용한 주소로 가게된다. 참 편리하니 프로젝트 설정할 때 꼭 해줘야겠다.
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 트랜지션이 작동하면서 애니메이션이 실행된다.
오늘은 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을 통해 기존 데이터를 유지하고,count만 cartItem.count 만큼 더한 새로운 객체인 updateItem을 만든다.axios.put()을 통해 해당 아이템의 정보를 서버에 업데이트 요청한다.반대로 existingItem이 존재하지 않는 경우(즉, 장바구니에 없는 상품이라면):
axios.post()를 통해 새 상품을 장바구니에 추가한다.이 모든 과정에서 오류가 발생하면 catch 블록에서 로그를 찍고, 에러를 다시 던진다(throw) → 상위에서 처리 가능하게 하기 위함이다.