[26-1] 카카오 맵 API (Kakao Map API)
[26-2] 태그 이용
[26-3] refetch의 문제점과 개선 방법
💡 구글 vs 네이버 vs 카카오
우리나라에서 가장 많이 사용하는 것은 구글, 네이버, 카카오에서 제공하는 지도 API다.
세 가지 API 간에는 제공하는 기능의 종류, 비용 문제 등의 차이가 있다.
차후 서비스를 직접 개발하게 되면, 이러한 차이점을 고려하여 어떤 API를 사용할 지 선택하면 된다.
📂 카카오 개발자 (Kakao Developers)
📂 카카오 맵 그리기
useEffect
: 페이지가 마운트되고 document 객체가 생성된 이후에 카카오맵을 호출할 수 있도록 변경해준다.declare const window: typeof globalThis & { kakao: any; };
import { useEffect } from "react"; declare const window: typeof globalThis & { kakao: any; }; export default function KaKaoMapPage(): JSX.Element { useEffect(() => { const script = document.createElement("script"); script.src = JavaScript 앱 키 document.head.appendChild(script); script.onload = () => { window.kakao.maps.load(function () { const container = document.getElementById("map"); // 지도를 담을 영역의 DOM 레퍼런스 const options = { // 지도를 생성할 때 필요한 기본 옵션 center: new window.kakao.maps.LatLng(37.241547, 131.864797), // 지도의 중심좌표. level: 3, // 지도의 레벨(확대, 축소 정도) }; const map = new window.kakao.maps.Map(container, options); // 지도 생성 및 객체 리턴 console.log(map); const imageSrc = "/images/15-19-47.png", // 마커이미지의 주소입니다 imageSize = new window.kakao.maps.Size(100, 100), // 마커이미지의 크기입니다 imageOption = { offset: new window.kakao.maps.Point(27, 69) }; // 마커이미지의 옵션입니다. 마커의 좌표와 일치시킬 이미지 안에서의 좌표를 설정합니다. const markerImage = new window.kakao.maps.MarkerImage( imageSrc, imageSize, imageOption ), markerPosition = new window.kakao.maps.LatLng(37.241547, 131.864797); // 마커가 표시될 위치입니다 // 마커를 생성합니다 var marker = new window.kakao.maps.Marker({ position: markerPosition, image: markerImage, // 마커이미지 설정 }); // 마커가 지도 위에 표시되도록 설정합니다 marker.setMap(map); }); }; }, []); return ( <> <div id="map" style={{ width: 1000, height: 900 }}></div> </> ); }
🎯 JavaScript API Key와 같은 민감 정보를 github에 올려도 될까?
- 안 된다. env를 이용한 환경변수 설정 등의 방법 등으로 최대한 숨기고, 절대 노출되면 안되는 중요 민감 정보의 경우에는 프론트엔드 서버에 두지 말고 백엔드 서버에 놓고 사용하는 편이 안전하다.
📂 a 태그
📂 router
📂 Next.js의 Link 태그
import { useRouter } from "next/router"; import Link from "next/link"; export default function KakaoMapRoutingPage() { // const router = useRouter(); // const onClickMoveToMap = () => { // router.push("/29-03-kakao-map-routed"); // }; return ( <div> {/* <button onClick={onClickMoveToMap}>맵으로 이동하기 !</button> */} <Link href="/29-03-kakao-map-routed"> <a>맵으로 이동하기 !!</a> </Link> </div> ); }
Link 안에 a 태그를 넣으면 시맨틱 요소를 가지고 있는 html 태그로 렌더링이 되기 때문에 웹 표준이나 검색 엔진 최적화 차원에서도 이점을 가지고 있다.
그렇기 때문에 가능한 부분에서는 가급적 Link 태그를 사용하는 것이 좋다.
💡 efetchQueries 언제 쓰는걸까?
- 작은 서비스에서는 오히려 refetchQueries를 쓰시는게 좋다.
- 코드의 가독성 면에 있어서는 refetchQueries가 훨씬 깔끔하고 좋기 때문에 성능을 크게 따질 필요없는 작은 서비스에서는 오히려 refetchQueries를 쓰시는게 좋다.
- 하지만 규모가 커지게 되면 서버의 부하를 초래할 수 있으므로 그때는 cache를 업데이트 하시는게 좋다.
refetchQueries
import { useQuery, gql, useMutation } from "@apollo/client"; import { IQuery, IQueryFetchBoardsArgs, } from "../../src/commons/types/generated/types"; const FETCH_BOARDS = gql` query fetchBoards($page: Int) { fetchBoards(page: $page) { _id writer title contents } } `; // 캐시에 저장되는 데이터와 요청 후 받아오는 값이 일치되어야 합니다. const CREATE_BOARD = gql` mutation createBoard($createBoardInput: CreateBoardInput!) { createBoard(createBoardInput: $createBoardInput) { _id writer title contents } } `; const DELETE_BOARD = gql` mutation deleteBoard($boardId: ID!) { deleteBoard(boardId: $boardId) } `; export default function StaticRoutedPage() { const { data } = useQuery<Pick<IQuery, "fetchBoards">, IQueryFetchBoardsArgs>( FETCH_BOARDS ); const [deleteBoard] = useMutation(DELETE_BOARD); const [createBoard] = useMutation(CREATE_BOARD); //삭제 함수 const onClickDelete = (boardId: string) => () => { void deleteBoard({ variables: { boardId }, refetchQueries: [{ query: FETCH_BOARDS }] }); }; //등록 함수 const onClickCreate = () => { void createBoard({ variables: { createBoardInput: { writer: "영희", password: "1234", title: "제목입니다~~", contents: "내용입니다@@@", }, }, refetchQueries: [{ query: FETCH_BOARDS }], }); }; return ( <> {data?.fetchBoards.map((el) => ( <div key={el._id}> <span style={{ margin: "10px" }}>{el.writer}</span> <span style={{ margin: "10px" }}>{el.title}</span> <button onClick={onClickDelete(el._id)}>삭제하기</button> </div> ))} <button onClick={onClickCreate}>등록하기</button> </> ); }
cache-state
import { useQuery, gql, useMutation } from "@apollo/client"; import { MouseEvent } from "react"; import { IQuery, IQueryFetchBoardsArgs, } from "../../src/commons/types/generated/types"; const FETCH_BOARDS = gql` query fetchBoards($page: Int) { fetchBoards(page: $page) { _id writer title contents } } `; // 캐시에 저장되는 데이터와 요청 후 받아오는 값이 일치되어야 합니다. const CREATE_BOARD = gql` mutation createBoard($createBoardInput: CreateBoardInput!) { createBoard(createBoardInput: $createBoardInput) { _id writer title contents } } `; const DELETE_BOARD = gql` mutation deleteBoard($boardId: ID!) { deleteBoard(boardId: $boardId) } `; export default function StaticRoutedPage() { const { data } = useQuery<Pick<IQuery, "fetchBoards">, IQueryFetchBoardsArgs>( FETCH_BOARDS ); const [deleteBoard] = useMutation(DELETE_BOARD); const [createBoard] = useMutation(CREATE_BOARD); //삭제 함수 const onClickDelete = (boardId: string) => () => { void deleteBoard({ variables: { boardId }, update(cache, { data }) { // 캐시를 수정한다는 뜻의 cache.modify cache.modify({ // 캐시에있는 어떤 필드를 수정할 것 인지 key-value 형태로 적어줍니다. fields: { fetchBoards: (prev, { readField }) => { const deletedId = data.deleteBoard; // 삭제된ID const filteredPrev = prev.filter( (el) => readField("_id", el) !== deletedId // el._id가 안되므로, readField를 사용해서 꺼내오기 ); return [...filteredPrev]; // 삭제된ID를 제외한 나머지 9개만 리턴 }, }, }); }, }); }; //등록 함수 const onClickCreate = () => { void createBoard({ variables: { createBoardInput: { writer: "영희", password: "1234", title: "제목입니다~~", contents: "내용입니다@@@", }, }, update(cache, { data }) { // 캐시를 수정한다는 뜻의 cache.modify cache.modify({ // 캐시에있는 어떤 필드를 수정할 것 인지 key-value 형태로 적어줍니다. fields: { fetchBoards: (prev) => { return [data.createBoard, ...prev]; }, }, }); }, }); }; return ( <> {data?.fetchBoards.map((el) => ( <div key={el._id}> <span style={{ margin: "10px" }}>{el.writer}</span> <span style={{ margin: "10px" }}>{el.title}</span> <button onClick={onClickDelete(el._id)}>삭제하기</button> </div> ))} <button onClick={onClickCreate}>등록하기</button> </> ); }