โœ๐Ÿป [Code Camp_TIL] 27์ผ์ฐจ: ์นด์นด์˜ค๋งต API, SPA & MPA, cache ์ง์ ‘ ์ˆ˜์ •ํ•˜๊ธฐ(update ์‚ฌ์šฉ)

code_Jยท2023๋…„ 4์›” 23์ผ
0

TIL

๋ชฉ๋ก ๋ณด๊ธฐ
33/41
post-thumbnail

์นด์นด์˜ค๋งต API

์šฐ๋ฆฌ๋‚˜๋ผ์—์„œ๋Š” ๋‹ค์–‘ํ•œ ์ง€๋„ API ์ค‘ ์นด์นด์˜ค, ๋„ค์ด๋ฒ„, ๊ตฌ๊ธ€ ์ง€๋„ API๋ฅผ ๋งŽ์ด ์‚ฌ์šฉํ•œ๋‹ค. 3๊ฐ€์ง€ ์ค‘ ๋น„์šฉ์ด๋‚˜ ์ƒํ™ฉ์— ๋งž๋Š” API๋ฅผ ์„ ํƒํ•˜๋ฉด ๋œ๋‹ค. ๋‚˜๋Š” ๊ทธ ์ค‘ ์นด์นด์˜ค ๋งต API๋ฅผ ์ด์šฉํ•ด๋ดค๋‹ค.


์นด์นด์˜ค ๊ฐœ๋ฐœ์ž(Kakao Developers)

์นด์นด์˜ค ๊ฐœ๋ฐœ์ž ํŽ˜์ด์ง€์— ์ ‘์†ํ•˜๋ฉด ์ง€๋„๋ฅผ ํฌํ•จํ•œ ๋‹ค์–‘ํ•œ API๊ฐ€ ์ œ๊ณต๋˜๊ณ  ์žˆ๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค. ๋‚˜์ค‘์— ํ•„์š”ํ•  ๋•Œ ์จ๋ณด๋ฉด ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค.

๋‚ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ถ”๊ฐ€ > ๋ฌธ์„œ > ์ง€๋„ > ์ขŒ์ธก ์‚ฌ์ด๋“œ๋ฐ”์˜ ์ง€๋„ ํด๋ฆญ > ๋ธŒ๋ผ์šฐ์ € ์„ ํƒ > ์™ผ์ชฝ ํ•˜๋‹จ์˜ ๋ฉ”๋‰ด๋ฐ”์˜ ํ‚ค ๋ฐœ๊ธ‰ ๋ฉ”๋‰ด ํด๋ฆญ > ์ƒ์„ฑํ•ด๋†“์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํด๋ฆญ > ์šฉ๋„์— ๋งž๋Š” ํ‚ค ์„ ํƒ > Guide ๋ณด๊ณ  ๋”ฐ๋ผํ•˜๊ธฐ


์นด์นด์˜ค ๋งต ๊ตฌํ˜„

1. Head ์„ค์ •

import Head from 'next/head';

Head ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๊ณ , ์•ˆ์— ์นด์นด์˜ค ๋งต script๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋Š” ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.

return(
	 <>
			<Head>
				<script type="text/javascript" src="//dapi.kakao.com/v2/maps/sdk.js?appkey='JavaScript ์•ฑ ํ‚ค ์ž…๋ ฅ'"></script>
			</Head>
	 </>
)

2. ๋งต ๊ทธ๋ฆฌ๊ธฐ

๋งต์„ ํ˜ธ์ถœํ•  JS ์ฝ”๋“œ์™€ ๋งต์„ ํ˜ธ์ถœ ๋ฐ›์•„ ์ถœ๋ ฅํ•  ์˜์—ญ(div)์„ ์„ค์ •ํ•ด์ค€๋‹ค.

const container = document.getElementById('map'); 
//์ง€๋„๋ฅผ ๋‹ด์„ ์˜์—ญ์˜ DOM ๋ ˆํผ๋Ÿฐ์Šค

const options = { 
//์ง€๋„๋ฅผ ์ƒ์„ฑํ•  ๋•Œ ํ•„์š”ํ•œ ๊ธฐ๋ณธ ์˜ต์…˜
    center: new kakao.maps.LatLng(33.450701, 126.570667), //์ง€๋„์˜ ์ค‘์‹ฌ์ขŒํ‘œ.
    level: 3 //์ง€๋„์˜ ๋ ˆ๋ฒจ(ํ™•๋Œ€, ์ถ•์†Œ ์ •๋„)
};

new kakao.maps.Map(container, options); 
//์ง€๋„ ์ƒ์„ฑ ๋ฐ ๊ฐ์ฒด ๋ฆฌํ„ด

// return(
	// ...
		<div id='map' style={{ "width" : "500px", "height" : "400px" }}></div>
// )

3. ๋‚ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์‚ฌ์ดํŠธ ๋„๋ฉ”์ธ ๋“ฑ๋ก

ํ‚ค ๋ฐœ๊ธ‰ > ๋‚ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ > ํ”Œ๋žซํผ > web ์‚ฌ์ดํŠธ ๋„๋ฉ”์ธ ๋“ฑ๋ก


4. ํŽ˜์ด์ง€ ์ด๋™ ํ›„ ์ง€๋„ ์ถœ๋ ฅํ•ด๋ณด๊ธฐ

ํŽ˜์ด์ง€ ์ด๋™์„ ํ•˜๋ฉด ์ง€๋„๊ฐ€ ๋ณด์ด์ง€ ์•Š๊ณ  ์—๋Ÿฌ๊ฐ€ ๋œฌ๋‹ค. ํ•˜์ง€๋งŒ ์ฃผ์†Œ์ฐฝ์— ํ•ด๋‹น ์ฃผ์†Œ๋ฅผ ์ž…๋ ฅํ•ด์„œ ์ ‘์†์„ ํ•˜๋ฉด ์ง€๋„๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ๋œฌ๋‹ค. ์™œ ๊ทธ๋Ÿฐ ๊ฑธ๊นŒ? ์ด๊ฒƒ์„ ์ดํ•ดํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” SPA์™€ CSR์„ ์•Œ์•„์•ผ ํ•œ๋‹ค.



SPA(single page application)


์œ„์—์„œ ๋ฐœ์ƒํ–ˆ๋˜ ์—๋Ÿฌ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์ฝ”๋“œ๋ฅผ ๊ฐ€์ ธ์™€๋ดค๋‹ค.

import { useRouter } from "next/router";

export default function KakaoMapRoutingPage() {
  const router = useRouter();
  const onClickMoveToMap = () => {
    router.push("/29-03-kakao-map-routed");
  };

  return (
    <div>
      <button onClick={onClickMoveToMap}>๋งต์œผ๋กœ ์ด๋™ํ•˜๊ธฐ!</button>
    </div>
  );
}

์œ„ ์ฝ”๋“œ์—์„œ router ๋Œ€์‹  a ํƒœ๊ทธ๋ฅผ ์ด์šฉํ•˜๋ฉด ์–ด๋–จ๊นŒ? ์•„๋ฌด๋Ÿฐ ๋ฌธ์ œ ์—†์ด ์ง€๋„๊ฐ€ ์ถœ๋ ฅ๋œ๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด router์™€ a ํƒœ๊ทธ๋Š” ๋ฌด์Šจ ์ฐจ์ด์ผ๊นŒ?



MPA

a ํƒœ๊ทธ๋ฅผ ํด๋ฆญํ•œ๋‹ค๋ฉด, ํ”„๋ก ํŠธ์—”๋“œ ์„œ๋ฒ„์— ์ ‘์†ํ•ด์„œ ํ•ด๋‹น ํŽ˜์ด์ง€๋ฅผ ๋‹ค์šด๋กœ๋“œ ๋ฐ›์•„์™€ ๋ธŒ๋ผ์šฐ์ €์—์„œ ๊ทธ๋ ค์ค€๋‹ค.

์ฆ‰, ํƒœ๊ทธ๋ฅผ ํด๋ฆญํ•  ๋•Œ๋งˆ๋‹ค ์ƒˆ๋กœ์šด ํŽ˜์ด์ง€๋ฅผ ๋‹ค์šด๋กœ๋“œ ๋ฐ›์•„์™€์„œ ๋ณด์—ฌ์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ํŽ˜์ด์ง€๊ฐ€ ์—ฌ๋Ÿฌ ์žฅ ์žˆ๋Š” ๊ฒƒ๊ณผ ๊ฐ™๋‹ค. ์ด๋Ÿฌํ•œ ๋ฐฉ์‹์„ MPA(multi page application)๋ผ๊ณ  ํ•œ๋‹ค.

MPA ์˜ ๊ฒฝ์šฐ, ํŽ˜์ด์ง€ ์ด๋™ ์‹œ๋งˆ๋‹ค ์„œ๋ฒ„์— ์š”์ฒญํ•ด์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์™€์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์„ฑ๋Šฅ์€ ์ข‹์ง€ ์•Š๋‹ค.


SPA

๋ฐ˜๋ฉด react์—์„œ๋Š” SPA(single page application) ๋ฐฉ์‹์œผ๋กœ ์ž‘๋™ํ•œ๋‹ค. ์ฒ˜์Œ ์ ‘์†ํ•  ๋•Œ ํ•ด๋‹น ํŽ˜์ด์ง€ ์™ธ์— ๋‹ค๋ฅธ ํŽ˜์ด์ง€๊นŒ์ง€ ํ•œ๋ฒˆ์— ๋‹ค์šด๋กœ๋“œ ๋ฐ›์•„์™€์„œ ํ•„์š”ํ•œ ํŽ˜์ด์ง€๋งŒ ๋ฝ‘์•„์„œ ๋ธŒ๋ผ์šฐ์ €์— ๋ณด์—ฌ์ค€๋‹ค.

SPA๋Š” ์ฒ˜์Œ ๋‹ค์šด๋กœ๋“œ ๋ฐ›์„ ๋•Œ๋Š” ์กฐ๊ธˆ ๋Š๋ฆด ์ˆ˜ ์žˆ์ง€๋งŒ, ํŽ˜์ด์ง€๋ฅผ ์ด๋™ํ•  ๋•Œ ๊ฑธ๋ฆฌ๋Š” ์‹œ๊ฐ„์ด MPA์— ๋น„ํ•˜์—ฌ ์••๋„์ ์œผ๋กœ ์งง๋‹ค.

MPA๋Š” ์ „ํ†ต์ ์ธ ์˜๋ฏธ์˜ ํ™ˆํŽ˜์ด์ง€๊ณ , SPA๋Š” ํ™ˆํŽ˜์ด์ง€๋ณด๋‹ค๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋Š๋‚Œ์ด ๊ฐ•ํ•˜๋‹ค.


kakao map undefined error ํ•ด๊ฒฐ


์œ„์—์„œ kakao map undefined ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋˜ ์ด์œ ๋Š”, ์ง€๋„๋ฅผ ๋‹ค์šด๋กœ๋“œ ๋ฐ›์•„์˜ค๋Š” ๋™์•ˆ ์ด๋™ํ•œ ํŽ˜์ด์ง€๋ฅผ ๋จผ์ € ๊ทธ๋ ค์คฌ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

a ํƒœ๊ทธ๋ฅผ ์ด์šฉํ•˜๋ฉด ์˜ค๋ฅ˜๋Š” ํ•ด๊ฒฐ๋˜์ง€๋งŒ, ํŽ˜์ด์ง€๋ฅผ ์ด๋™ํ–ˆ์„ ๋•Œ ํŽ˜์ด์ง€ ์ž์ฒด๊ฐ€ ์ƒˆ๋กœ ๋กœ๋”ฉ๋˜๊ธฐ ๋•Œ๋ฌธ์— SPA ํ”„๋ ˆ์ž„์›Œํฌ์ธ Next.js๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜๋ฏธ๊ฐ€ ์—†์–ด์ง„๋‹ค.


๋Œ€์‹  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 ํƒœ๊ทธ๋ฅผ ๋„ฃ์œผ๋ฉด semantic ์š”์†Œ๋ฅผ ๊ฐ–๊ณ  ์žˆ๋Š” html ํƒœ๊ทธ๋กœ ๋ Œ๋”๋ง ๋œ๋‹ค. ์˜๋ฏธ๊ฐ€ ์žˆ๋Š” semantic tag๋Š” ๊ฒ€์ƒ‰ ๋…ธ์ถœ์ด ์‰ฌ์›Œ์ง„๋‹ค๋Š” ์žฅ์ ์ด ์žˆ๋‹ค. ๋˜ํ•œ MPA์ธ a ํƒœ๊ทธ๋ณด๋‹ค SPA์ธ Link ํƒœ๊ทธ๊ฐ€ ์†๋„๋„ ํ›จ์”ฌ ๋น ๋ฅด๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ€๊ธ‰์ ์ด๋ฉด Link ํƒœ๊ทธ๋ฅผ ์“ฐ๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.


๋˜ํ•œ, ์ด๋™ํ•œ ํŽ˜์ด์ง€์—์„œ script ๋‹ค์šด๋กœ๋“œ๋ฅผ ์™„๋ฃŒํ•˜๊ณ  ์นด์นด์˜ค ๋งต ๋กœ๋“œ๋„ ์™„๋ฃŒ๋œ ํ›„์— ์ง€๋„๊ฐ€ ๋ถˆ๋Ÿฌ์ ธ ์˜ค๋„๋ก ํ•ด์ค˜์•ผ ํ•œ๋‹ค.

useEffect(() => {
  // ์—ฌ๊ธฐ์„œ ์ง์ ‘ ๋‹ค์šด๋กœ๋“œ ๋ฐ›๊ณ , ๋‹ค ๋ฐ›์„๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ ธ๋‹ค๊ฐ€ ๊ทธ๋ ค์ฃผ๊ธฐ!!
  const script = document.createElement("script"); // html์— script๋ผ๋Š” ํƒœ๊ทธ(Element)๋ฅผ ๋งŒ๋“ ๋‹ค.
  script.src =
    "//dapi.kakao.com/v2/maps/sdk.js?appkey='JavaScript API Key'&autoload=false";
  document.head.appendChild(script);

  script.onload = () => {
		window.kakao.maps.load(function () {
			const container = document.getElementById("map");
      const options = {
        center: new window.kakao.maps.LatLng(33.450701, 126.570667),
        level: 3,
      };
      const map = new window.kakao.maps.Map(container, options);
		}
	}
}



refetch์˜ ๋ฌธ์ œ์ ๊ณผ ๊ฐœ์„  ๋ฐฉ๋ฒ•

์ง€๊ธˆ๊นŒ์ง€๋Š” ๋“ฑ๋ก/์‚ญ์ œ ์ดํ›„ refetch๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ์—…๋ฐ์ดํŠธํ•ด์™”๋‹ค. ํ•˜์ง€๋งŒ refetch๋Š” ๊ทธ๋ ‡๊ฒŒ ์ข‹์€ ๋ฐฉ๋ฒ•์€ ์•„๋‹ˆ๋‹ค.

์™œ๋ƒํ•˜๋ฉด useQuery()๋Š” ์‹คํ–‰๋œ ํ›„ cache-state์— ์ €์žฅ๋˜๋Š”๋ฐ refetch๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด api ์š”์ฒญ์„ ๋‹ค์‹œ ๋ฐ›์•„์˜ค๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๋น„ํšจ์œจ์ ์ธ ๋ฐฉ๋ฒ•์ด๋‹ค.

๋”ฐ๋ผ์„œ ์ด์ œ๋ถ€ํ„ฐ๋Š” refetchํ•˜์ง€ ์•Š๊ณ , apollo-cache-state๋ฅผ ์ง์ ‘ ์—…๋ฐ์ดํŠธ ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•  ๊ฒƒ์ด๋‹ค.


์บ์‹œ ์ง์ ‘ ์ˆ˜์ •ํ•˜๊ธฐ


์บ์‹œ๋ฅผ ์ง์ ‘ ์ˆ˜์ •ํ•  ๋•Œ์—๋Š” update(){}๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. ์›๋ž˜ refetch๋ฅผ ์‚ฌ์šฉํ–ˆ๋˜ ๋ถ€๋ถ„์„ update๋กœ ๋ฐ”๊พธ์—ˆ๋‹ค.

// ์บ์‹œ์— ์ €์žฅ๋˜๋Š” ๋ฐ์ดํ„ฐ์™€ ์š”์ฒญ ํ›„ ๋ฐ›์•„์˜ค๋Š” ๊ฐ’์ด ์ผ์น˜๋˜์–ด์•ผ ํ•จ
const CREATE_BOARD = gql`
  mutation createBoard($createBoardInput: CreateBoardInput!) {
    createBoard(createBoardInput: $createBoardInput) {
      _id
      writer
      title
      contents
    }
  }
`;


export default function StaticRoutedPage() {
  
//์‚ญ์ œ ํ•จ์ˆ˜
  const onClickDelete = (boardId: string) => () => {
    void deleteBoard({
      variables: { boardId },
      update(cache, { data }) {
				// ์บ์‹œ๋ฅผ ์ˆ˜์ •ํ•œ๋‹ค๋Š” ๋œป์˜ cache.modify
        cache.modify({
				// ์บ์‹œ์—์žˆ๋Š” ์–ด๋–ค ํ•„๋“œ๋ฅผ ์ˆ˜์ •ํ•  ๊ฒƒ ์ธ์ง€ key-value ํ˜•ํƒœ๋กœ ์ ์–ด์ฃผ๊ธฐ
          fields: {
            fetchBoards: (prev, { readField }) => {
              // prev: ์ด์ „๋ฐ์ดํ„ฐ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ
              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>
    </>
  );
}

refetchQueries๋Š” ๋ฌด์กฐ๊ฑด ์“ฐ์ง€ ๋ง์•„์•ผ ํ• ๊นŒ?

ํ•˜์ง€๋งŒ ๋ฌด์กฐ๊ฑด refetchQueries๋ฅผ ์“ฐ์ง€ ๋ง๋ผ๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๋‹ค! ์ƒํ™ฉ์— ๋งž๊ฒŒ ๊ฒฐ์ •ํ•˜๋ฉด ๋œ๋‹ค. ์ž‘์€ ์„œ๋น„์Šค์—์„œ๋Š” refetch๋ฅผ ์“ฐ๋Š” ๊ฒƒ์ด ๋” ๋‚˜์„ ์ˆ˜๋„ ์žˆ๋‹ค. ์ฝ”๋“œ๋„ ํ›จ์”ฌ ์งง๊ธฐ ๋•Œ๋ฌธ์— ์„ฑ๋Šฅ์„ ํฌ๊ฒŒ ๋”ฐ์งˆ ํ•„์š”๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ์—๋Š” refetch๊ฐ€ ๋‚˜์„ ์ˆ˜๋„ ์žˆ๋‹ค.



profile
Web FE ๊ฐœ๋ฐœ์ž ์ทจ์ค€์ƒ

0๊ฐœ์˜ ๋Œ“๊ธ€