
useMemo, 화면 이동 시 데이터 전달, 우편번호 서비스
지난 시간에 이어 북스토어 프로젝트의 도서 주문과 주문 목록 화면을 구현하는 과정을 정리했습니다.
useMemo 훅은 성능 최적화를 위해 연산된 값을 재사용할 수 있게 도와줍니다. 연산된 값을 메모리에 저장해두고, 의존성 배열에 있는 값이 바뀌지 않았다면 이전에 연산한 값을 그대로 재사용합니다. 값이 변경되었을 때만 다시 연산을 수행하기 때문에 불필요한 렌더링과 연산 부하를 줄일 수 있습니다.
import { useMemo } from 'react';
function OrderSummary({ cartItems }: Props) {
// cartItems가 바뀔 때만 총액을 다시 계산합니다
const totalPrice = useMemo(() => {
return cartItems.reduce((sum, item) => sum + item.price * item.quantity, 0);
}, [cartItems]);
return <div>총 금액: {totalPrice.toLocaleString()}원</div>;
}
useMemo 를 사용할 때는 모든 곳에 남발하지 않는 것이 중요합니다. 단순한 연산에는 오히려 메모이제이션 비용이 더 클 수 있기 때문에, 연산이 복잡하거나 렌더링이 잦은 곳에 선택적으로 적용하는 것이 좋습니다.
화면을 이동하면서 특정 데이터를 함께 넘겨주어야 하는 상황이 생길 수 있습니다. 이때 react-router-dom 에서 제공하는 useNavigate 와 useLocation 훅을 활용할 수 있습니다.
// Basket.tsx (데이터를 보내는 쪽)
import { useNavigate } from 'react-router-dom';
function Basket() {
const navigate = useNavigate();
const handleOrder = () => {
// state 속성을 통해 이동할 경로로 데이터를 함께 넘겨줍니다
navigate('/order', { state: orderData });
};
return <button onClick={handleOrder}>주문하기</button>;
}
// Order.tsx (데이터를 받는 쪽)
import { useLocation } from 'react-router-dom';
function Order() {
const location = useLocation();
// location.state를 통해 전달받은 데이터를 꺼내서 사용합니다
const orderDataFromBasket = location.state;
return <div>{/* 주문 정보 렌더링 */}</div>;
}
form 태그 내부에서 button 태그를 사용할 경우, 기본 타입이 submit 으로 자동 지정됩니다. 폼 제출 목적이 아닌 단순 클릭 버튼으로만 사용하고 싶다면 반드시 type="button" 을 명시해야 합니다.
// ❌ type을 명시하지 않으면 form이 제출됩니다
<button onClick={handleAddress}>주소 검색</button>
// ✅ type="button"을 명시해야 합니다
<button type="button" onClick={handleAddress}>주소 검색</button>
주소 검색 기능을 구현하기 위해 별도의 키 발급이나 사용량 제한이 없는 Daum 우편번호 서비스를 활용했습니다.
해당 서비스를 이용하려면 외부 스크립트를 불러와야 합니다. 하지만 이 스크립트는 프로젝트 전체가 아닌 주문 페이지에서만 필요하므로, index.html 에 전역으로 추가하는 대신 컴포넌트가 마운트될 때만 동적으로 로드하도록 구현했습니다. 불필요한 리소스를 줄여 초기 로딩 성능을 개선할 수 있습니다.
const SCRIPT_URL = '//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js';
interface Props {
onCompleted: (address: string) => void;
}
function AddressSearch({ onCompleted }: Props) {
useEffect(() => {
// 컴포넌트가 마운트될 때 스크립트를 동적으로 추가합니다
const script = document.createElement('script');
script.src = SCRIPT_URL;
script.async = true;
document.head.appendChild(script);
// 컴포넌트가 언마운트될 때 스크립트를 제거합니다
return () => {
document.head.removeChild(script);
};
}, []);
const handleOpen = () => {
new window.daum.Postcode({
onComplete: (data: { address: string }) => {
onCompleted(data.address);
},
}).open();
};
return (
<button type="button" onClick={handleOpen}>
주소 검색
</button>
);
}
useEffect 의 클린업 함수에서 스크립트를 제거하는 부분이 핵심입니다. 컴포넌트가 언마운트될 때 불필요하게 남아있는 스크립트 태그를 정리해 메모리 누수를 방지합니다.