[TIL] 8. KakaoMap 코드로 이해한 useRef와 useEffect

MinseoKim·2026년 8월 10일

1. 오늘의 한 줄 요약

오늘은 Hook의 사용법보다 각 Hook이 왜 필요한지를 조금 더 이해한 날이었다.


useState, useRef, useEffect를 따로 외울 때는 계속 헷갈렸다.

오늘 수업을 마치고 KakaoMap api 연결 실습 코드를 뜯어 보면서 세 Hook의 역할을 조금 더 명확하게 이해했다.

  • State는 화면에 영향을 주는 값을 관리한다
  • Ref는 다시 렌더링할 필요 없이 유지해야 하는 대상을 기억한다
  • Effect는 특정 시점이나 값의 변화에 맞춰 작업을 실행한다

실습코드 KakaoMap.jsx에서는 지도와 마커 객체를 useRef에 저장했다.
강사님이 제공해준 코드에서는 총 두번의 useEffect 훅을 사용했는데,

  • 첫 번째 useEffect에서는 지도를 처음 만들고
  • 두 번째 useEffect에서는 moveTo가 바뀔 때 기존 지도를 해당 위치로 이동시키는 역할이었다.

2. 오늘 배운 내용

오늘 처음 배운 useRef를 보고 hook 간 문법 개념이 헷갈려서 이 내용을 TIL에 정리해보고자 한다.

1) useStateuseRef는 언제 사용해?

처음에는 둘의 차이를 이렇게만 알고 있었다.

useState
→ 값이 바뀌면 리렌더링

useRef
→ 값이 바뀌어도 리렌더링하지 않음

틀린 설명은 아니지만
실제 코드를 작성할 때는 이 기준만 가지고 둘을 구분하기 어려웠다.

오늘은 아래 질문으로 구분해보기로 했다.

useState

이 값이 바뀌면 화면도 달라져야 하는가?

그렇다면 useState를 사용한다.

예를 들어 댓글 입력값은 state가 적합하다.

const [comment, setComment] = useState('');

사용자가 글자를 입력하면 comment가 바뀐다.
입력창에도 변경된 값이 바로 보여야 한다.

댓글 목록도 마찬가지다.

const [comments, setComments] = useState([]);

댓글을 추가하거나 삭제하면 화면의 댓글 목록도 달라져야 한다.

이런 값은 useState로 관리한다.


useRef

반대로 화면을 다시 그릴 필요는 없지만 계속 기억해야 하는 값도 있다.

KakaoMap에서는 다음 두 객체를 ref에 저장하고 있었다.

const mapRef = useRef(null);
const markerRef = useRef(null);

처음에는 둘 다 null이다.

지도와 마커를 생성한 뒤 아래처럼 저장한다.

mapRef.current = map;
markerRef.current = marker;

이제 각각의 ref에는 다음 값이 들어있다.

mapRef.current
→ 생성된 Kakao Map 객체

markerRef.current
→ 생성된 Kakao Marker 객체

지도 위치를 변경한다고 해서 새로운 지도를 계속 만들 필요는 없다.

처음 만든 지도 객체를 기억해두고 다시 가져와서 위치만 바꾸면 된다.

이런 경우 useRef를 사용할 수 있다.


2) useRef는 DOM에만 사용하는 게 아니었다

처음에는 useRef가 HTML 요소에 접근하기 위한 Hook이라고 생각했다.

예를 들어 input에 자동으로 포커스를 주는 경우다.

const inputRef = useRef(null);

<input ref={inputRef} />

이후 다음 코드를 실행할 수 있다.

inputRef.current.focus();

여기서 inputRef.current는 실제 <input> 요소를 가리킨다.

그래서 focus()처럼 HTML 요소가 가진 기능을 직접 실행할 수 있다.

하지만 KakaoMap 코드를 보면서 useRef가 꼭 DOM만 저장하는 것은 아니라는 걸 알게 됐다.

const mapRef = useRef(null);
const markerRef = useRef(null);

여기에는 HTML 요소가 들어가는 것이 아니다.

Kakao Maps API가 생성한 지도 객체와 마커 객체가 들어간다.

그래서 앞으로 useRef를 이렇게 기억해야한다

컴포넌트가 유지되는 동안 같은 값을 계속 기억해야 하지만 그 값의 변경 때문에 화면을 다시 그릴 필요는 없을 때 사용한다.

DOM 요소도 그중 하나인것이다.

DOM은 화면에 실제로 만들어지는 html 요소(<input/>)라고 생각하니 편했다.
리액트는 글자를 바꾸고 싶을때, 직접 html을 찾는게 아니라 state를 변경한다. 이게 지금까지 수업 시간에 반복적으로 따라한 setter함수이다.
내 코드에서 setter 함수를 써서 state를 변경하면, 리액트가 실제 DOM으로 변경해주는 것이다.
(이제야.. 리엑트의 state와 DOM을 조금 알 것 같다..)


3) useEffect는 값을 저장하는 Hook이 아니다

useStateuseRef를 구분하고 나니 이번에는 useEffect가 헷갈렸다.

KakaoMap 코드를 따라가면서 역할을 나눠보니 생각보다 단순했다.

useState
→ 상태를 관리

useRef
→ 대상이나 객체를 기억

useEffect
→ 특정 시점이나 변화에 맞춰 작업 실행

useEffect는 값을 저장하는 것이 아니다.

어떤 일이 생긴 뒤 해야 할 작업을 실행하는 역할에 가깝다.


3. KakaoMap 코드로 Hook 흐름 이해하기

오늘 사용한 KakaoMap 코드에는 useEffect가 두 개 있었다.

첫 번째 useEffect — 지도 처음 만들기

useEffect(() => {
    window.kakao.maps.load(() => {
        navigator.geolocation.getCurrentPosition((position) => {
            let lat = position.coords.latitude;
            let lng = position.coords.longitude;

            const container = document.getElementById('map');

            const centerPosition =
                new window.kakao.maps.LatLng(
                    37.5665,
                    126.9780
                );

            const map = new window.kakao.maps.Map(
                container,
                {
                    center: centerPosition,
                    level: 3
                }
            );

            const marker =
                new window.kakao.maps.Marker({
                    position: centerPosition
                });

            marker.setMap(map);

            mapRef.current = map;
            markerRef.current = marker;
        });
    });
}, []);

이 effect의 역할은 지도를 처음 만드는 것이다.

흐름을 정리하면 다음과 같다.

Kakao Maps SDK 로드
↓
현재 위치 요청
↓
지도를 넣을 영역 찾기
↓
지도 생성
↓
마커 생성
↓
지도와 마커를 ref에 저장

지도 객체를 생성한 뒤에는 아래 코드로 기억해둔다.

mapRef.current = map;
markerRef.current = marker;

이후에는 새로운 지도를 만들지 않아도 된다.

필요할 때 mapRef.current를 통해 기존 지도 객체를 다시 사용할 수 있다.


4) []를 단순히 "한 번 실행"이라고만 외우지 않기

기존에는 아래 코드를 보면 이렇게 외웠다.

useEffect(() => {
    loadData();
}, []);

[]가 있으니까 처음 한 번 실행한다.

결과만 보면 맞다.

하지만 오늘은 []다시 실행할 기준이 되는 값이 없는 상태라고 이해했다.

반대로 이렇게 작성하면:

useEffect(() => {
    // ...
}, [moveTo]);

moveTo가 effect를 다시 실행할 기준이 된다.

즉 아래처럼 읽을 수 있다.

moveTo가 달라졌다면 이 effect를 다시 실행해라.


5) 두 번째 useEffectmoveTo가 바뀌면 지도 이동

useEffect(() => {
    if (!moveTo) return;
    if (!mapRef.current || !markerRef.current) return;

    const lat = parseFloat(moveTo.lat);
    const lng = parseFloat(moveTo.lng);

    const position =
        new window.kakao.maps.LatLng(lat, lng);

    mapRef.current.setCenter(position);
    markerRef.current.setPosition(position);
}, [moveTo]);

여기서는 moveTo를 보고 있다.

[moveTo]

부모에서 새로운 위치가 전달되면 effect가 다시 실행된다.

예를 들어 moveTo가 아래 값으로 바뀌었다고 하자.

{
    lat: 35.1796,
    lng: 129.0756
}

새로운 좌표를 만든다.

const position =
    new window.kakao.maps.LatLng(lat, lng);

그리고 기존 지도를 가져온다.

mapRef.current.setCenter(position);

마커도 기존 객체를 가져와 이동시킨다.

markerRef.current.setPosition(position);

여기서 중요한 점은 지도를 다시 만드는 것이 아니라는 것이다.

처음 만들어둔 지도를 ref로 기억하고 있다가 위치만 바꾼다.

전체 흐름은 아래처럼 볼 수 있다.

moveTo 변경
↓
useEffect 실행
↓
새 위치 생성
↓
mapRef에서 기존 지도 가져오기
↓
지도 이동
↓
markerRef에서 기존 마커 가져오기
↓
마커 이동

6) useRefuseEffect가 헷갈렸던 이유

처음에는 지도가 움직이는 코드를 보면서 이런 생각이 들었다.

"지도를 움직이는 기능이니까 useRef를 사용하는 건가?"

하지만 정확히는 역할이 다르다.

moveTo
→ 어디로 이동해야 하는지 알려주는 값

mapRef
→ 어떤 지도를 움직일지 기억

useEffect
→ moveTo가 바뀌었을 때 지도 이동을 실행

useRef가 지도를 움직이는 것이 아니다.

useRef는 기존 지도 객체를 기억하고 있을 뿐이다.

실제로 움직이는 부분은 아래 코드다.

mapRef.current.setCenter(position);

그리고 이 코드를 moveTo가 바뀔 때 실행하도록 연결하는 것이 useEffect다.


7) ref가 아직 준비되지 않았을 수도 있다

두 번째 effect에는 아래 코드가 있다.

if (!mapRef.current || !markerRef.current) return;

처음에는 단순한 예외 처리라고 생각했다.

하지만 지도와 마커는 첫 번째 effect가 실행된 후 만들어진다.

따라서 지도 초기화가 끝나기 전에 moveTo가 바뀐다면 이런 상태일 수도 있다.

mapRef.current === null

이때 아래 코드를 실행하면 문제가 생긴다.

mapRef.current.setCenter(position);

그래서 먼저 지도와 마커가 만들어졌는지 확인한다.

if (!mapRef.current || !markerRef.current) return;

즉 아래 의미다.

아직 지도와 마커가 없다면 지금은 실행하지 않는다.


8) 지도 클릭 이벤트

지도에는 클릭 이벤트도 등록했다.

window.kakao.maps.event.addListener(
    map,
    "click",
    function (mouseEvent) {
        const lat = mouseEvent.latLng.getLat();
        const lng = mouseEvent.latLng.getLng();

        marker.setPosition(
            new window.kakao.maps.LatLng(lat, lng)
        );

        setWeatherByCoords(lat, lng);
    }
);

사용자가 지도를 클릭하면 클릭한 위치의 위도와 경도를 가져온다.

그 좌표로 마커를 이동시킨다.

마지막으로 setWeatherByCoords()에 좌표를 전달한다.

지도 클릭
↓
클릭한 위치의 위도·경도 확인
↓
마커 이동
↓
해당 좌표를 날씨 조회 로직에 전달

4. 트러블 슈팅

카카오 지도 SDK를 등록했는데 지도가 뜨지 않았다 🥶

KakaoMap을 구현하면서 처음 막힌 부분은 지도 호출이었다.

index.html에 Kakao Maps SDK를 등록했지만 지도가 화면에 나오지 않았다.

처음에는 SDK 경로나 코드 문제라고 생각했다.

그래서 브라우저 개발자 도구에서 네트워크 요청을 확인했다.

요청 결과는 403 에러였다.

상세 에러 메시지를 확인해보니 코드 문제가 아니었다.
Kakao Developers에서 카카오맵 서비스를 활성화하지 않은 상태였다.

원인

  • index.html에 SDK는 등록되어 있었음
  • 지도 호출은 계속 실패함
  • 네트워크 탭에서 403 Forbidden 확인
  • Kakao Developers의 카카오맵 기능이 비활성화 상태였음

해결 방법


1. Kakao Developers에 접속
2. 내 애플리케이션에서 MapTest 앱을 선택한다.
3. 제품 설정 → 카카오맵(Kakao Maps)으로 이동한다.
4. 카카오맵 활성화 설정을 ON으로 변경한다.
5. 설정을 저장한다.
6. 페이지를 새로고침한 뒤 다시 실행한다.

설정을 변경한 뒤에는 지도 호출이 정상적으로 성공했다.

이번 문제를 겪으면서 외부 API에서 불러오는 sdk status를 확인하는 것이 트러블 슈팅의 중요한 지표임을 알게되었다

앞으로는 아래 순서로 먼저 확인하는 습관이 필요하다고 느꼈다.

코드 확인
↓
브라우저 콘솔 확인
↓
Network 상태 코드 확인
↓
상세 에러 메시지 확인
↓
외부 서비스 설정 확인

특히 403처럼 요청 자체는 서버에 도착했지만 거부된 경우에는 코드 문법만 계속 고치는 것보다 권한이나 서비스 설정부터 확인하는 편이 빠를 수 있다는 걸 배웠다.

실습 결과

  • kakaomap이 영문 city 객체를 인식하지 못했다. 그래서 국문으로 변경해 해결했다.



  • kakaomap은 해외 지역을 지원하지 않기 때문에 파리 버튼 선택시 에서는 디버그 콘솔이 출력되는 것을 확인할 수 있었다.

오늘 가장 크게 이해한 부분

오늘은 Hook을 하나씩 외우는 것보다 코드 안에서 역할을 나눠보는 게 훨씬 이해하기 쉬웠다.

지금은 아래처럼 정리하고 있다.

useState
→ 화면에 영향을 주는 값을 관리

useRef
→ 다시 렌더링하지 않고 계속 기억해야 하는 대상을 관리

useEffect
→ 특정 시점이나 값의 변화에 맞춰 작업 실행

KakaoMap으로 보면 더 단순하다.

moveTo
→ 어디로 이동할 것인가

mapRef
→ 어떤 지도를 움직일 것인가

useEffect
→ moveTo가 바뀌면 지도 이동

앞으로 Hook을 선택할 때도 문법부터 생각하기보다 먼저 역할을 생각해보려고 한다.

화면에 반영해야 하는 값인가?

그렇다면 useState.

다시 렌더링할 필요 없이 기억해야 하는 대상인가?

그렇다면 useRef.

어떤 시점이나 변화 뒤에 실행해야 하는 작업인가?

그렇다면 useEffect.


5. 다음 할 일

useState, useRef, useEffect의 역할은 전보다 많이 구분됐다.

다음에는 useEffect를 조금 더 깊게 공부해보려고 한다. 그리고 지금은 이벤트 핸들러 함수를 실습코드에서 사용했지만, 관련 내용을 배우지 못했다. 그래서 따로 공부할 필요가 있을 것 같다.

  • useEffect가 정확히 언제 실행되는지
  • [][value]의 차이 -> 단순 실행 횟수 차이인가..?
  • 의존성 배열을 잘못 작성했을 때 생기는 문제
  • 이벤트 핸들러에서 바로 처리하는 것과 useEffect로 처리하는 것의 차이

참고자료
https://velog.io/@hyunjine/useState-vs-useRef


[메모]cmd 명령어(서버 열기)
npx json-server --watch db.json --port 5002

0개의 댓글