[TS] 카카오 API로 주소 검색하기

김zunyange·2023년 9월 22일
0

TypeScript

목록 보기
2/3
post-thumbnail

현재 진행하고 있는 프로젝트의 회원가입 및 개인정보 수정 기능에서 '주소 검색' (혹은 '우편번호 찾기')을 클릭했을 때, 카카오 주소를 연동하여 실제 주소 API를 사용하기로 했다. 그래서 검색해봤더니 리액트나 자바스크립트에 대한 설명만 많고, 물론 타입스크립트가 자바스크립트 기반이기는 하지만 확실한 타입 코드를 알고 싶었기에 내가 올려보려고 한다!!

1. Script 불러오기

🪄 HTML에서 script 태그는 body 의 최하단에 삽입되는 것이 좋다.

//index.html
<body>
  <script src="//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>
</body>

2. Interface 선언

타입스크립트는 컴파일 언어이기 때문에, IDE에서 바로 오류를 감지한다. 그렇기 때문에 외부 객체는 타입을 알 수 없기 때문에 바로 참조할 수가 없고 Interface를 선언해 주어야 한다.

declare global {
  interface Window {
    daum: any;
  }
}

interface IAddr {
  address: string;
  zonecode: string;
}

// 다른 파일에 따로 정의해둬서 import 한다면,
declare global {
  export interface Window {
    daum: any;
  }
}
export interface IAddr {
  address: string;
  zonecode: string;
}
  • 위와 같이 window에 daum 객체가 있다고 명시해 준다.
  • 주소 입력 후 반환받기 위한 Parameter도 interface 선언해 준다.

3. 클릭 이벤트 선언

주소 찾기에 사용되는 onClick 이벤트 함수를 정의한다.

 const onClickAddr = () => {
    new window.daum.Postcode({
      oncomplete: function (data: IAddr) {
        (document.getElementById("addr") as HTMLInputElement).value =
          data.address;
        (document.getElementById("zipNo") as HTMLInputElement).value =
          data.zonecode;
        document.getElementById("addrDetail")?.focus();
      },
    }).open();
  };

4. 에러 발생 !

온클릭 기능이 잘 작동하는지 확인하려고 했는데, 에러가 뜨는 것도 아니고 아무런 변화가 없어서 console을 찍어봤는데!

    const onClickAddr = () => {
    new window.daum.Postcode({
      oncomplete: function (data: IAddr) {
        (document.getElementById('addr') as HTMLInputElement).value =
          data.address;
        (document.getElementById('zipNo') as HTMLInputElement).value =
          data.zonecode;
        document.getElementById('addrDetail')?.focus();
      }
    }).open();
    console.log('ddddd');
  };
//
return (
  <S.StyledButton size={ButtonSize.LARGE} onClick={onClickAddr}>
    주소 검색
  </S.StyledButton>
)
//

콘솔도 찍히지 않아 타입왕 솜이님께 여쭤봤다.
그랬더니!!! 우리는 class101 UI 라이브러리의 Button 스타일을 사용하고 있어서 클래스101 에서 지정한 props 를 써줘야 한다고 하셨다.

이렇게 클래스 101에서 지정해준 타입과 나의 이벤트함수의 타입을 맞춰줘야 한다. 원래는 컴파일 과정에서 에러가 뜨는데, 우리는 또 스타일 컴포넌트로 감싸줬기 때문에 에러가 안뜨는거라고 하셨다!! 대박. 처음 알았다 ..

따라서, 클래스101에서 지정해준 타입을 사용하려면, 아래와 같이 수정해줘야 한다.

import { MouseEvent } from 'react'; // 1. 추가해주고

const onClickAddr = (event: MouseEvent<HTMLElement>) => {
  // 2. 인자에 MouseEvent 추가해주면 됨! 
    new window.daum.Postcode({
      oncomplete: function (data: IAddr) {
        (document.getElementById('addr') as HTMLInputElement).value =
          data.address;
        (document.getElementById('zipNo') as HTMLInputElement).value =
          data.zonecode;
        document.getElementById('addrDetail')?.focus();
      }
    }).open();
  };

최종 코드

import React, { FC, useState, useEffect, MouseEvent, ChangeEvent } from 'react';
import * as S from './AddressEdit.style';
import { ButtonSize, Checkbox, Caption1 } from '@class101/ui';
import { StyledInput } from '../../Styles/common.style';
import { PaymentShowRecipientProps } from '../../types/components';
import { IAddr } from '../../types/components';

//components.ts
declare global {
  export interface Window {
    daum: any;
  }
}
export interface IAddr {
  address: string;
  zonecode: string;
}
//

const AddressEdit: FC<PaymentShowRecipientProps> = ({
  showRecipient,
  data,
  width,
  addressData
}) => {
  const [isChecked, setIsChecked] = useState(false);
  const [addrValue, setAddrValue] = useState(''); // 도로명주소
  const [zipNoValue, setZipNoValue] = useState(''); // 우편번호
  const [addrDetailValue, setAddrDetailValue] = useState(''); // 상세주소

  const handleAddrDetail = (event: ChangeEvent<HTMLInputElement>) => {
    setAddrDetailValue(event.target.value);
  };

  const onClickAddr = (event: MouseEvent<HTMLElement>) => {
    new window.daum.Postcode({
      oncomplete: function (data: IAddr) {
        setAddrValue(data.address);
        setZipNoValue(data.zonecode);
        setAddrDetailValue('');
        document.getElementById('addrDetail')?.focus();
      }
    }).open();
  };

  useEffect(() => {
    if (addressData) {
      setAddrValue(addressData.address || '');
      setZipNoValue(String(addressData.postCode || '')); //우편번호는 숫자니까
      setAddrDetailValue(addressData.addressDetail || '');
    } else {
      setAddrValue('');
      setZipNoValue('');
      setAddrDetailValue('');
    }
  }, [addressData]);

  return (
    <S.AddressEditContainer width={width}>
      {data ? ( // data : 배송지가 채워져있을 때
        <>
          <StyledInput
            label="배송지이름 *"
            defaultValue={addressData?.addressName}
          />
          {showRecipient && (
            <StyledInput
              label="수령인 *"
              defaultValue={addressData?.recipient}
            />
          )}
          <StyledInput
            label="휴대폰번호 *"
            defaultValue={addressData?.phoneNumber}
          />
          {!showRecipient && (
            <S.StyledCheckBox>
              <Checkbox
                checked={isChecked}
                size={14}
                onChange={() => setIsChecked(!isChecked)}
              />
              <Caption1>기본배송지 저장</Caption1>
            </S.StyledCheckBox>
          )}
          <S.StyledBox>
            <StyledInput
              disabled
              label="우편번호"
              defaultValue={addressData?.postCode}
            />
            <S.StyledButton size={ButtonSize.LARGE}>주소 검색</S.StyledButton>
          </S.StyledBox>
          <StyledInput
            label="도로명주소 *"
            defaultValue={addressData?.address}
          />
          <StyledInput
            label="상세주소 *"
            defaultValue={addressData?.addressDetail}
          />
        </>
      ) : (
        <>
          <StyledInput
            label="배송지이름 *"
          />
          {showRecipient && (
            <StyledInput
              label="수령인 *"
            />
          )}
          <StyledInput
            label="휴대폰번호 *"
          />
          {!showRecipient && (
            <S.StyledCheckBox>
              <Checkbox
                checked={isChecked}
                size={14}
                onChange={() => setIsChecked(!isChecked)}
              />
              <Caption1>기본배송지 저장</Caption1>
            </S.StyledCheckBox>
          )}
          <S.StyledBox>
            <StyledInput
              disabled
              label="우편번호"
              id="zipNo"
              type="text"
              value={zipNoValue}
            />
            <S.StyledButton size={ButtonSize.LARGE} onClick={onClickAddr}>
              주소 검색
            </S.StyledButton>
          </S.StyledBox>
          <StyledInput
            label="도로명주소 *"
            id="addr"
            type="text"
            value={addrValue}
          />
          <StyledInput
            label="상세주소 *"
            id="addrDetail"
            type="text"
            value={addrDetailValue}
            onChange={handleAddrDetail}
          />
        </>
      )}
    </S.AddressEditContainer>
  );
};

export default AddressEdit;

선택한 도로명주소 및 우편번호가 input 값에 자동으로 들어가고 컨트롤하기 위해서 value 속성을 사용해줘야 하며, value 의 특징은 다음과 같다.

  • React 컴포넌트 내에서 입력 요소와 관련된 상태를 관리하고 실시간으로 업데이트하기 위해 사용
  • 사용자 입력과 컴포넌트의 상태를 연결하여 데이터 흐름을 유지할 때 중요한 역할

따라서 useState hook 을 사용하여 각 입력 요소의 값을 관리함으로써 React 컴포넌트에서 입력 요소의 동작과 데이터 흐름을 제어할 수 있다.


🏷 출처
React[카카오 주소 API] TypeScript에서 카카오(다음) 주소 API 사용 방법

profile
배움은 즐거워 ~(*ૂ❛ᴗ❛*ૂ)

1개의 댓글

comment-user-thumbnail
2023년 9월 25일

옹..~ 저번에 같이 본게 이거였군여! 멋쟁이 준영님 남은 프로젝트도 파잇팅 🚀

답글 달기