React

카카오 맵 API

// 카카오 맵 지도 연동하기
// 카카오 개발자 사이트 ..!
import Head from "next/head";
import { useEffect } from "react";

declare const window: typeof globalThis & {
  kakao: any;
};
// 윈도우에 카카오라는것도 있어! 하고 알려쥬기

export default function kakaoMapPage() {
  useEffect(() => {
    // 3. api, 빈 div 받고 나서 그다음에 그 안에 지도를 그려주기 위해서 useEffect 사용

    // 4. 지도 띄우는 코드 작성
    const container = document.getElementById("map"); //지도를 담을 영역의 DOM 레퍼런스
    // container라는 상수 값에 map의 이름을 가진 id 태그를 가져와 담아주기
    const options = {
      // 지도를 생성할 때 필요한 기본 옵션 (초기값 설정)
      center: new window.kakao.maps.LatLng(37.4847, 126.9027), //지도의 중심좌표.
      level: 3, //지도의 레벨(확대, 축소 정도)
    };

    const map = new window.kakao.maps.Map(container, options); //지도 생성 및 객체 리턴
    // container라는 div태그에 options을 그려줘
  }, []);

  return (
    <>
      <Head>
        <script
          type="text/javascript"
          src="//dapi.kakao.com/v2/maps/sdk.js?appkey=12a523204d108e11d65325b65bc2213a"
        ></script>
        {/* 2. 실제 지도를 그리는 api 불러오기 */}
      </Head>
      <div id="map" style={{ width: 500, height: 400 }}></div>
      {/* 1.지도 담을 영역 만들기 */}
    </>
  );
}

그런데 버튼을 클릭해서 페이지를 이동한 뒤 카카오맵이 출력되도록 하면 오류가 나게 된다

Router를 사용하지 않고 html의 a 태그를 사용하면 오류가 안나고 잘 작동됨

어떤 차이가 있는거지????

router를 이용하는 경우

SPA(single page application)

spa에서는 서비스에 처음 접속할 때 모든 페이지의 데이터를 다 받아온다
그리고 router를 통해 페이지를 이동할 때, 실제로는 페이지의 일부에 해당하는 컴포넌트만 교체한 뒤 페이지를 다시 그려오게 된다 (re-rendering)

페이지를 이동할 때 걸리는 시간이 압도적으로 짧음

a태그를 이용하는 경우

MPA(multi page application)

서로 다른 url을 가진 페이지들이 각각 독립적으로 존재함
프론트엔드 서버 페이지를 그린 뒤 브라우저로 html, css, js를 보내주는 작업을 매 페이지 이동시마다 거치게 된다
(주소를 입력해서 이동하는 것과 a 태그를 통해 이동하는 것은 동일한 기능)

mpa는 페이지 이동시마다 서버에 요청해서 데이터를 받아와야하기 때문에 느림

a 태그를 사용하면 페이지 이동시 오류가 사라지지만, 페이지를 이동했을 때 페이지 자체가 새로 로딩되기 때문에 SPA 프레임워크인 Next.js를 사용하는 의미가 없어진다.

해결하기 위해서는 ?

링크 태그 이용해주고
script 다운로드를 완료하고 카카오 맵 로드도 완료한 뒤에 지도를 불러오도록 코드 수정해주기

export default function kakaoMapPage() {
  useEffect(() => {
    // 3. api, 빈 div 받고 나서 그다음에 그 안에 지도를 그려주기 위해서 useEffect 사용

    const script = document.createElement("script");
    // 5. 이동하기 버튼 클릭시 오류 수정하기 위해서 스크립트 태그 만들어주기
    script.src =
      "//dapi.kakao.com/v2/maps/sdk.js?appkey=12a523204d108e11d65325b65bc2213a&autoload=false";
    // 6. 스크립트의 src에 이 주소를 넣을거야
    document.head.appendChild(script);
    // 7. Head의 자식에 script를 넣을거야

    script.onload = () => {
      // 8.  window.kakao가 로드되면 그때 이 함수 실행시켜줘
      window.kakao.maps.load(function () {
        // v3가 모두 로드된 후, 이 콜백 함수가 실행됩니다.
        // 4. 지도 띄우는 코드 작성
        const container = document.getElementById("map"); //지도를 담을 영역의 DOM 레퍼런스
        // container라는 상수 값에 map의 이름을 가진 id 태그를 가져와 담아주기
        const options = {
          // 지도를 생성할 때 필요한 기본 옵션 (초기값 설정)
          center: new window.kakao.maps.LatLng(37.4847, 126.9027), //지도의 중심좌표.
          level: 3, //지도의 레벨(확대, 축소 정도)
        };
        const map = new window.kakao.maps.Map(container, options); //지도 생성 및 객체 리턴
        // container라는 div태그에 options을 그려줘
        console.log(map);
      });
    };
  }, []);
  
    return (
    <div>
      {/* <button onClick={onClickMoveToMap}>맵으로 이동하기 !</button> */}
      <Link href="/29-03-kakao-map-routed">
        <a>맵으로 이동하기 !!</a>
      </Link>
    </div>
  );
}

Link 안에 a 태그를 넣으면 **시맨틱 요소**를 가지고 있는 html 태그로 렌더링이 되기 때문에 웹 표준이나 검색 엔진 최적화 차원에서도 좋음

그렇기 때문에 가능한 부분에서는 가급적 Link 태그를 사용하는 것이 좋다 !
Link 사용이 불가능할 때만 router.push 쓰기!
(특정 게시물을 등록, 삭제, 수정 한 다음 자동으로 페이지 이동시켜줄 때는 router push, 근데 이제 버튼을 클릭해서 이동하고 싶을 때는 link 태그 쓰면 됨 (메뉴 눌러서 이동시키기 등등))

시맨틱 태그란? 그 자체 만으로도 `**의미를 담고 있는 html 태그**`
많이 사용되는 시맨틱 태그에는 **header, footer, nav, section, aside** 등이 있음

callback

callback 함수란? : 다른 함수의 인자로 들어가는 함수

function aaa(qqq){
	// 함수 로직
}

aaa(function(){})

위의 코드에서 aaa 함수의 인자에 들어가는 function(){} 를 callback 함수라고 한다
화살표 함수로 바꾸면?

aaa(() => {})

callback 함수를 사용하는 이유는? : 특정한 요청이 끝난 뒤, 그 결과 값을 가지고 다른 요청을 실행시켜야 할 때

function aaa(qqq){
	// 외부 API에 데이터 요청하는 로직
	// ...
	// ...
	// 요청 끝!
	const result = "요청으로 받아온 데이터 결과값"
	qqq(result) // 요청 끝나면 qqq 실행시키기
}

aaa(result) => {
	console.log("요청이 끝났습니다.")
	console.log("요청으로 받아온 데이터는" + result + "입니다")
}

async/await 가 없었을 때 비동기를 동기적으로 처리하기 위해서 callback을 많이 사용했음!

const onClickCallback = () => {
    // axios 없을 때 사용하던 방식
    // aaa야 api 로드 다 되면, 이 함수 실행시켜줘! 하고 callback 함수 집어넣기
    const aaa = new XMLHttpRequest(); // 서버와 상호 작용하기 위해 사용되는 객체
    aaa.open("get", `http://numbersapi.com/random?min=1&max=200`);
    aaa.send();
    aaa.addEventListener("load", (res: any) => {
      const num = res.target.response.split(" ")[0]; // 랜덤 숫자 가지고오기

      const bbb = new XMLHttpRequest();
      bbb.open("get", `http://koreanjson.com/posts/${num}`); // 랜덤으로 가지고 온 숫자에 해당하는 게시물 불러오기
      bbb.send();
      bbb.addEventListener("load", (res: any) => {
        // 가지고 온 데이터가 문자열로 되어있어서 id만 뽑아오기 힘들기 때문에 객체로 바꿔줌
        const userId = JSON.parse(res.target.response).UserId;

        const ccc = new XMLHttpRequest();
        ccc.open("get", `http://koreanjson.com/posts?userId=${userId}`); // 뽑아온 id가 작성한 게시물 보기
        ccc.send();
        ccc.addEventListener("load", (res: any) => {
          console.log(JSON.parse(res.target.response)); // 최종 결과값!
        });
      });
    });
  };

이렇게 하면.. 콜백지옥에 빠진다 그래서 다음으로 나온게 promise !!

promise

axios
      .get("http://numbersapi.com/random?min=1&max=200")
      .then((res) => {
        const num = res.data.split(" ")[0];
        return axios.get(`https://koreanjson.com/posts/${num}`);
      })
      .then((res) => {
        const userId = res.data.UserId;
        // prettier-ignore
        return axios.get(`https://koreanjson.com/posts?userId=${userId}`)
      })
      .then((res) => {
        console.log(res.data);
      });

promise를 사용할 경우 각 요청들이 체인처럼 연결되는데,
이걸 프로미스 체인 또는 프로미스 체이닝(Promise chaining) 이라고 부름
그런데 Promise도 콜백 지옥은 해결했지만, 직관적이지 못한 문제가 있음

그래서 나온게!! Async/Await

Async / Await

 const onClickAsyncAwait = async () => {
    // prettier-ignore
    const res1 = await axios.get("http://numbersapi.com/random?min=1&max=200");
    const num = res1.data.split(" ")[0];

    console.log("여기는 2번입니다~");
    const res2 = await axios.get(`https://koreanjson.com/posts/${num}`);
    const userId = res2.data.UserId;

    // prettier-ignore
    const res3 = await axios.get(`https://koreanjson.com/posts?userId=${userId}`)
    console.log(res3.data);
 };

async / await를 이용하면 코드가 직관적이고 심플해짐 !

async / await는 promise에만 붙일 수 있다!
axios가 promise 객체를 이용해 만들어진 라이브러리이기 때문에 async/await를 사용할 수 있는 것

매크로 태스크 큐 vs 마이크로 태스크 큐

promise로 실행하는 함수는 태스트 큐에 들어간다

  • 매크로 태스크 큐 : setTimeout, setInterval 등
  • 마이크로 태스크 큐 : Promise

태스크 큐들 간의 실행 순서는? : 매크로, 마이크로가 부딪힐 경우 마이크로 태스트 큐가 우선순위

export default function EventLoopWithQueuePage() {
  const onClickTimer = () => {
    console.log("===============시작~~===============");

    // 비동기 작업 (매크로큐에 들어감)
    setTimeout(() => {
      console.log("저는 setTimeout! 매크로 큐!! 0초 뒤에 실행될 거예요!!!");
    }, 0);

    new Promise((resolve) => {
      resolve("철수");
    }).then((res) => {
      console.log("저는 Promise! - 1!! 마이크로 큐!! 0초 뒤에 실행될 거예요!!!");
    });

    // 비동기 작업 (매크로큐에 들어감)
    setInterval(() => {
      console.log("저는 setInterval! 매크로 큐!! 1초 마다 계속 실행될 거예요!!!");
    }, 1000);

    let sum = 0;
    for (let i = 0; i <= 9000000000; i += 1) {
      sum = sum + 1;
    }

    new Promise((resolve) => {
      resolve("철수");
    }).then((res) => {
      console.log("저는 Promise! - 2!! 마이크로 큐!! 0초 뒤에 실행될 거예요!!!");
    });

    console.log("===============끝~~===============");
  };

  return <button onClick={onClickTimer}>시작</button>;
}

Promise와 setInterval, setTimeout를 함께 실행시키면 이렇게 마이크로 큐가 먼저 실행되는걸 확인 할 수 있음

profile
어제보다 오늘 발전하는 프론트엔드 개발자

0개의 댓글