폼에서 주소를 직접 입력받으면 표기가 제각각이라 관리가 번거롭다.
그래서 이번에는 다음 주소검색(카카오 우편번호 서비스) 을 붙여서 사용자가 주소를 검색 후 선택하도록 구성했다.
먼저 다음 주소검색 스크립트를 불러와야 한다.
<script src="//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>
이 스크립트를 불러오면 window.daum.Postcode 를 사용할 수 있다.
다음 주소검색은 npm 패키지를 import 해서 쓰는 방식이 아니라,
외부 스크립트를 로드한 뒤 window.daum.Postcode 전역 객체를 사용하는 방식이다.
그래서 별도 import는 필요 없고, TypeScript에서는 아래처럼 전역 타입 선언을 추가해두면 된다
declare global {
interface Window {
daum?: {
Postcode: new (options: {
oncomplete: (data: DaumPostcodeData) => void;
onclose?: (state: "FORCE_CLOSE" | "COMPLETE_CLOSE") => void;
}) => {
open: () => void;
};
};
}
}
any 로 넓게 선언할 수도 있지만,
실제로 사용하는 Postcode 구조만 명시해두면 자동완성과 타입 체크 측면에서 더 안전하다.
주소 검색은 아래처럼 열 수 있다.
new window.daum.Postcode({
oncomplete: (data) => {
console.log(data);
},
}).open();
type SelectedRegion = {
addressLabel: string | null;
region1DepthName: string | null;
region2DepthName: string | null;
region3DepthName: string | null;
};
type OpenDaumAddressSearchParams = {
onSelect: (region: SelectedRegion) => void;
};
export const openDaumAddressSearch = ({
onSelect,
}: OpenDaumAddressSearchParams) => {
if (!window.daum?.Postcode) {
throw new Error("Daum Postcode script is not loaded.");
}
new window.daum.Postcode({
oncomplete: (data) => {
onSelect({
addressLabel: data.roadAddress || data.jibunAddress || null,
region1DepthName: data.sido || null,
region2DepthName: data.sigungu || null,
region3DepthName: data.bname || null,
});
},
}).open();
};
버튼 클릭 시 주소검색을 열고, 선택한 값을 상태에 반영하면 된다.
const handleOpenAddressSearch = async () => {
try {
openDaumAddressSearch({
onSelect: (selectedRegion) => {
setField("mainRegion", {
...selectedRegion,
// 사용자에게 따로 입력받을 상세 주소
detailAddress: mainRegion?.detailAddress ?? null,
});
},
});
} catch (error) {
console.error(error);
alert("주소 검색 창을 열지 못했습니다.");
}
};
다음 주소검색 사용 흐름은 단순하다.
1. 스크립트 로드
2. window.daum 전역 타입 선언
3. new window.daum.Postcode() 생성
4. open() 으로 팝업 열기
5. oncomplete 에서 선택 결과 받기
6. 필요한 형태로 상태 저장
주소를 직접 입력받는 것보다 훨씬 안정적이고,
폼에서도 재사용하기 좋다.