12/06 싱글페이지, CSR

김하은·2022년 12월 6일
0

네이버 지도, 카카오지도 등을 가져와 쓸수 있는 방법이 있었다. 각기 '네이버 개발자', '카카오개발자', 구글개발자' 등으로 검색하면 개발자용 사이트가 나오는데 그 회사 자체에서 사용하는 기능들을 외부에서도 사용할 수 있게끔 제공해준다.

조금 더 들어가 지도만 비교해보면 구글지도는 세계지도 중심이고, 네이버나 카카오는 국내중심이라고 볼 수 있다.
우리는 카카오 지도를 이용하였다.
카카오 지도부분 을 이용하기위해 찾아 들어가니 1일 30만회까지는 무료라고 되어있었다.

웹, 안드로이드, ios등 어디에서 보여줄 것인지에따라 달라지게되는데 우리는 웹에서 보여줄 것이기에 자바스크립트 부분을 클릭했다.

시작하기를 살펴보니 사이트 도메인을 등록하라는 말이 써있었다. 이용할 페이지 메인 주소를 의미했다.
즉, 우리는 실숩을 위해 localhost:3000을 사용하고있으니 해당 주소를 입력하면된다.
일단

왼편의 샘플을보니 상당히 많은 기능들이 있었다.

이것을 다 언제 사용해보나 했는데 다 사용하지는 못하더라도 어디까지 가능한지 등은 봐두는게 좋다고 하셨다.

깊은 내용은 Docs에 있다. (사용되는 함수등)

이제 시작해보자. 먼저 Guide를 보면 친절하게도 나와있다.

  1. 카카오 개발자사이트 회원가입하기

해당사이트에서 로그인하기를 누르면 회원가입페이지가 나왔다. 회원가입을 진행하고 로그인을 하면 위쪽에 내 어플리케이션생성? 이라는 것이 있는데, 이곳에 들어가 앱을 생성해주어야 키를 받아올 수 있다.

앱생성?: 각각의 개발자 사이트에서는 내 프로젝트 만들기를 해야한다.
포트폴리오마다 사용하는 기능 달라질 수 있어 각가그이 프로젝트를 나누어 등록할 수 있다고 한다. 이 프로젝트를 앱 또는 어플리케이션이라고 한다.

앱생성을 클릭하면 아이콘추가하기라는 곳이 있다. 현재는 안해도 되지만, 나중에 실서비스용으로 사용할 경우에는 넣어주어야하며 카카오측의 검수가 이루어질 수 있다고 한다.

이름은 아무거나 상관없이 지으면되고, 사업자명도 이름과 동일하게 생성해주었다.

이렇게 생성하는것은 모든 개발자 사이트들이 다 비슷하다고 한다.

  1. 생성된 앱을 클릭하고 아래쪽으로 내리면 플랫폼 등록하기가 있다. 들어가서 웹플랫폼등록하기 클릭

어떤 사이트에서 사용하는지 1일 30만회의 제한이 있기에 해당 카운트를 위해 적어주는것. 주소입력에
해당 실습주소인

http://localhost:3000
을 입력해준다.

이 플랫폼 등록을 꼭 해줘야 지도를 받아올수 있다는 점을 명심할 것.

그리고 다시 가이드로 돌아가 아래쪽 시작하기를 본다.

먼저 지도가 들어갈 영역을 그려달라고 써있다.

<div id="map" style="width:500px;height:400px;"></div>

해당 태그의 아이디값이 map.
그리고 실제 지도를 그리는 API를 가져온다.
script태그 내에 적어주는데, 기존에 html에서는 head태그안에 script태그를 적어주었는데, 이 head태그를 자바스크립트에서 시용하기 위해서는 next에서 제공되는 Head라는 태그를 사용하면된다.

Head태그 안에 두번째 단계에 있는 script태그를 복사해 넣는다

<script type="text/javascript" src="//dapi.kakao.com/v2/maps/sdk.js?appkey=발급받은 APP KEY를 넣으시면 됩니다."></script>

여기서 API키는 생성한 앱에 들어가면 위에 있는데 자바스크립트용 키를 복사해 붙여넣으면 된다.

그런데 아래쪽을 더 보니

'API를 로딩하는 스크립트 태그는 HTML파일안의 head, body 등 어떠한 위치에 넣어도 상관없습니다.
하지만, 반드시 실행 코드보다 먼저 선언되어야 합니다.'

라고 써 있는것을 볼 수 있다.
즉, API는 화면(돔)이 그려지기 전에 받아와야한다는 것이다. 받아와야 그려질 수 있기 때문이다.

즉 ComponentDidMount부분이라고 할 수있다.
해당 부분은 클래스컴포넌트 생명주기때 그려지고 나서 실행이라는 부분의 함수 부분이다. 함수형에서는 useEffect로 사용할 수 있었다. 해당부분을 잊어버려 무슨소리인가 하고 찾아보았다...

따라서 스크립트 태그는 원래 자리에 놓고, 실제 받아오는 부분은 useEffect를 사용해 넣어주었다.

var container = document.getElementById('map'); //지도를 담을 영역의 DOM 레퍼런스
var options = { //지도를 생성할 때 필요한 기본 옵션
	center: new kakao.maps.LatLng(33.450701, 126.570667), //지도의 중심좌표.
	level: 3 //지도의 레벨(확대, 축소 정도)
};

var map = new kakao.maps.Map(container, options);

이 부분이 useEffect로 들어가면된다.
다만 조금 수정하자면 아까, 지도 영역 부분을 style 태그로 주었는데, JSX에서는 style=을 하고 중괄호를 두개를 써서 작성한다.

style={{ width: 500, height: 400 }}

원래모양은 이렇다

const mystyle = {
	width:500;
    height:400;
  }  

원래 객체였기에 이 객체부분만 style={} 여기 중괄호에 들어가게되어 저렇게 중괄호가 두개인 모양이 되는것이다.

그리고 var 사용은 재할당 , 재선언이 가능한 문제가 있어 const나 let을 사용하기로했으니 해당부분은 고정해 사용할 것이기에 const 로 바꿔준다.

그런데 이렇게 써도 빨간줄이 잔뜩뜬다.
타입에러가 난것이라고 했다.
현재 스크립트 태그로 카카오라는것을 윈도우로 받아왔지만, 타입은 받아오지 못했기에 타입을 적어주어야한다.

맨 위쪽에 타입을 선언해준다.

declare const window: typeof globalThis & {
  kakao: any;
};

window를 글로벌 this라고 하는데 이게 무슨말인가 하면은 콘솔창에 this라고 입력만해도 window가 찍힌다.따라서 글로벌 this.

위에 써준 코드부분을 해석하면 window는 globalThis타입이고, 그 안에 kakao도 있다 라는 의미가된다. 해당kakao타입은 우리가 알 수 없으니 any로 적어준다. 그리고 kakao라는 부분 앞에 전부 window.을 붙여준다.

import Head from "next/head";
import { useEffect } from "react";

declare const window: typeof globalThis & {
  kakao: any;
};

export default function KaKaoMapPage() {
  useEffect(() => {
    const container = document.getElementById("map"); // 지도를 담을 영역의 DOM 레퍼런스
    const options = {
      // 지도를 생성할 때 필요한 기본 옵션
      center: new window.kakao.maps.LatLng(37.511826, 127.058388), // 지도의 중심좌표.
      level: 3, // 지도의 레벨(확대, 축소 정도)
    };

    const map = new window.kakao.maps.Map(container, options); // 지도 생성 및 객체 리턴
    // map의 빨간줄 없애기위해 한번 사용함
    console.log(map);
  }, []);
  return (
    <>
      <Head>
        <script
          type="text/javascript"
          src="//dapi.kakao.com/v2/maps/sdk.js?appkey=발급받은 APP KEY를 넣으시면 됩니다."
</script>
      </Head>
      <div id="map" style={{ width: 500, height: 400 }}></div>
    </>
  );
}

해당페이지에 접속하면 지도가 생성된것을 확인 가능하다.

다만, 만약 버튼을 이용해 해당 페이지로 들어가려하면 받아와지지못하는 상황을 마주치게된다.


싱글페이지 어플리케이션 이해

옛날 php방식등으로 웹사이트에 접속하게되면 버튼 클릭시 마다 그 페이지를 다운받아오기에 위에보면 로딩중처럼 뭔가 돌아가는것이 보인다. 즉, 버튼 클릭시마다 새로고침이 새롭게 되는 형식이라 느릴 수 밖에 없다.
이 방식은 멀티페이지 어플리케이션 이라고한다

그러나 요즈음에는, 브라우저요청시 해당페이지의 모든페이지를 한번에 다 받아오게 되고, 그 상태에서 해당부분만 잘라 보여주는 형식을 사용하게된다.

다시말해, 처음 주소입력등 접속시에만 서버에 접속해 다운받아오고 이후부터는 이미 다운받아온것을 사용하게된다.

따라서 이방법을 한번 모든것을 한페이지?로 받아와 사용한다는 의미로

싱글페이지 어플리케이션 이라고한다.

따라서 요즘방식으로 한다면 받아오고나서는 빨리 진행된다. 즉, 요즘 트랜드는 리엑트, 뷰, 앵귤러 등으로 만드는 싱글페이지 어플리케이션이다. 따라서, 다운받아오기도전에 실행되게되어 버튼을 눌러 페이지를 이동했을때 지도를 받아오지 못하고 에러를 일으키는 것이다.

앵커태그라고 불리는

<a> 

태그도 원래 태그 이동시 사용하던것인데 왜 리엑트에서는 사용하지 않는가?
해당 태그는 html에서 제공하는 테그로 새롭게 다운로드 받아오는 방식인 멀티페이지 방식을 사용하게되어 느리다.

리엑트, 앵귤러, 뷰 등에서 제공되는 기능을 사용해야 싱글페이지 방식으로 사용이 가능하다.

즉, 이것이 우리가 a태그가 아니라 router.push를 사용했던 이유였다.


CSR

=>Client Side Rendering

요청자인 브라우저(클라이언트라고함) 상에서 서버에서 데이터를 받아 화면을 그려주는 방식으로 한번에 미리 다운받아놓은 것에서 페이지가 이동되니 빠르다.

정리가 안되어 찾아보니 반대의 개념을 알게 되었다

SSR

==> Server Side Rendering

클라이언트가 서버에 매번 데이터를 요청하여 서버에서 처리하는것.

반대의 경우로 비교해보니 훨씬 이해가 쉬었다.

즉, CSR은 서버에서 한번에 받아와 클라이언트인 브라우저에서 실행되게되어 빠른것!!

버튼을 눌렀을때 바로 이동할 수 있는 router.push말고 다른 기능도 있는데 next에서 제공하는

<Link></Link>

이 Link태그이다.

<Link href="이동할 페이지 주소"></Link>

해당태그는 a태그와 같으나 CSR방식으로 작동된다.

이렇게보면 router.push를 버튼과 연결해 사용하는것과 Link라는 태그를 사용하는것 둘다 CSR방식이라는것을 알 수 있다.

router.push("이동할 페이지")
로 페이지 이동하기와
Link태그 사용으로 페이지 이동의 차이.
:Link태그안에는

<a>

이 앵커태그를 달아준다. 해당 a태그는 실제 기능은 하지 않는 태그로 검색엔진과 관련된 태그인데, 어떤것을 검색시에 검색엔진을 사용하는 업체들은 모든 사이트들을 다 돌아 점수를 매기는 프로그램을 만들게되고, 그 점수에따라 검색결과를 띄워주는 것인데, 상위에 노출되게 하는것을 검색엔진최적화(SEO)라고한다.

이때 앵커 태그(a태그)는 검색엔진에 련체이지와 링크태그의 a태그가 연관이 있다고 표시해주는 역할을 한다. 즉, 엔진 최적화를 위해 사용하는 태그이다.

Link태그를 사용하지 않는경우

  1. 버튼을 클릭해 이동하는것이아니라 특정 함수가 실행된 후 페이지가 이동되는경우: Link태그가 아니라 router.push사용.
  2. 페이지 이동하면서 로직추가를 하기위해. 방문페이지를 로컬스테이트에 저장하거나 클릭시 추가 로직을 실행하고 싶은 경우:router.push사용

이렇게 보니 1, 2 번 둘다 비슷한 경우인것같다.

정리하자면 페이지 이동전에 무언가를 실행하도록한다면 router.push를 사용해야된다는 말이다.

const router = useRouter();
const onClickMoveToMap = () => {
  void router.push(`26-03-kakao-map-routed`);
};
 <button onClick={onClickMoveToMap}>맵으로 이동하기</button> 

  <Link href="26-03-kakao-map-routed">
    <a>맵으로 이동하기</a>
  </Link>

버튼클릭시 지도 불러와지지않는 에러 해결하기

방법은 두가지가 있다.

  1. 일단 Head태그부분들을 복사해 다른 첫 시작 페이지부터 가져오기. 또는 전체 페이지가 실행되는 _app.tsx에 넣기. ==> 사용하지 않는 페이지에서도 불러오기에 다른 페이지들 받아올때 속도가 느려질 수 있다.
  2. 사용하는 페이지에서만 다운로드 받고, 기다리기로 바꾸기

1번 방법은 그리 좋지 않기에 2번 방법을 사용한다.

돔을 그리기전(화면을 그리기전) 스크립트가 모두 다운로드 될때까지 기다리기. useEffect에서 직접 스크립트 태그를 만들어준다. ==> 스크립트태그 조작위함.

그 만든 태그를 onload를 사용해 로드가 다 되면 실행되게 한다.

그리고 카카오에서도 설정이 필요하다.

Docs의 맨아래에 load부분에 kakao.maps.load부분을 참고하여 스크립트 src에 autoload-false를 추가해준다. 그리고 카카오맵 onload부분함수를 추가해준다.

 useEffect(() => {
    // html 에 script라는 테그 만듬. 변수에 저장해 조작하기 쉽게하기위해.
    const script = document.createElement("script"); // <script></script> 태그생성
    script.src =
    "//dapi.kakao.com/v2/maps/sdk.js?autoload=false&appkey=발급받은 APP KEY"; // script에 src추가
    document.head.appendChild(script); // head부분에 자식으로 스크립트 테그 추가해줘(변수에 담아서 변수를 사용한것)

    script.onload = () => {
      // 스크립트가 로드가 다 되고
      window.kakao.maps.load(function () {
        // 카카오가 다 로드가 되면 그때 해줘
        const container = document.getElementById("map"); // 지도를 담을 영역의 DOM 레퍼런스
        const options = {
          // 지도를 생성할 때 필요한 기본 옵션
          center: new window.kakao.maps.LatLng(37.511826, 127.058388), // 지도의 중심좌표.
          level: 3, // 지도의 레벨(확대, 축소 정도)
        };

        const map = new window.kakao.maps.Map(container, options); // 지도 생성 및 객체 리턴
        // map의 빨간줄 없애기위해 한번 사용함
        console.log(map);
      });
    };
  }, []);

autoload=false appkey=발급받은 APP KEY 해당부분은 주소안에 키를 추가한것으로

QueryString

이라고 한다.
현재 두개를 사용하고있는데, 두개 이상사용시 &를 붙여준다.

이렇게 보내게되면 백엔드에서 & 를 기준으로 자르고 =를 기준으로 잘라 사용하게된다.

** 주소는 띄어쓰기 없이 붙여서 사용한다.


refetch 사용안하기.

refetch를 사용하게되면 다시 API를 받아오는것이다. 원래 useQuery()등을 하게되면 cache-first로 글로벌스테이트에서 확인을 한번하고 있으면 더 요청하지 않고 저장된것을 사용하는데, 무조건 새로 받아오려면 network-only라는 설정을 해주어야했다.

refetch는 새로 받아오는 것으로 성능이 그다지 좋지는 못하다.
때문에 캐시를 직접수정하는 방법을 사용하기도 한다.

물론, 서비스 규모에따라 달라진다. 서비스가 작은경우에 API요청이 적으니 refetch를 사용하는것이 나을 수 있으나, 서비스가 커지면 그만큼 API요청이 많아질 수 있기에 캐시를 직접수정하는 방법을 사용한다.

다만 refetch 보다 복잡하긴 하다.

리페치 없이 목록 바로 업데이트되게하기

삭제하기클릭 ==> 삭제후 리페치 보내지 않고 캐시를 직접수정
등록하기클릭 ==> 등록후 리페치 보내지 않고 캐시를 직접수정

추가적인 리페치 없이 캐시 state직접수정하기

삭제할때는 어떤 게시글을 사용할지 게시글 id가 필요함. document.getElementById해서 id를 가져오거나, event.target.id를 해서 가져오거나 HOF방식으로 가져오기

          <button onClick={onClickDelete(el._id)}> 삭제하기</button>

(el._id)를 가지고 해당 onClick 함수에 넘겨주기

const onClickDelete = (boardId: string)

여기 boardId라는 부분으로 들어가게된다.

const onClickDelete = (boardId: string) => () => {

HOF방식은 함수안의 함수기에 앞에 소괄호와 화살표를 한번더 적어주고 그 자리에 들어간다.

쿼리에서 받아올 것은 재요청을 받아오지 않게 만들기 위해 기존의 형태와 형태를 맞춰주어야한다.

updateQuery말고 update를 사용한다.
사용해도 상관은 없는데 Docs상에서 deprecated 된다고 써있다고한다. deprecated란 더이상 추가 업데이트가 일어나지 않는다는 의미로 사용을 못한다는 뜻은 아니다. 따라서 update를 사용하는것 같다. (추가적인 기능 업데이트등의 지원을 위함)

원래 variables아래부분에 refetch를 적어주었었다.

삭제시에 다시 fetchBoards를 하여 목록을 다시 보여주고 , 등록시에도 다시 그렇게 목록을 업데이트한다.

따라서

// refetchQueries:[{query:FETCH_BOARDS}]

이렇게 다시 리페치로 fetchBoards를 요청했다.

그러나 이부분을 캐시조작방법으로 바꾸면

삭제부분
 update(cache, { data }) {
        cache.modify({
          fields: {
            fetchBoards: (prev, { readField }) => {
              const deletedId = data.deleteBoard; // 삭제된 아이디
              const filteredPrev = prev.filter(
                (el) => readField("_id", el) !== deletedId
              ); // 삭제된 아이디랑 다른것들만 남김// el._id가 안되므로 readField를 사용해 꺼내오기
              return [...filteredPrev]; // 삭제된 ID를 제외한 나머지만 리턴
            },
          },

cache부분에는 기존에 글로벌스테이트에있는 것을 가져오는 것이고, {data}는 최근에 받아온 것을 의미한다.
cache.modify로 캐시를 수정한다고 선언하고,fields 에 저장된 키 이름으로 어떤 키를 수정할 지를 불러온다. 우리는 fetchBoards를 리페치해 받아온것처럼 해당 fetchBoards 를 수정해야하니 해당부분을 조작해주기 위해 함수로 적어준다.

등록부분은

 update(cache, { data }) {
        cache.modify({
          fields: {
            fetchBoards: (prev) => {
              return [data.createBoard, ...prev];
            },
          },

기존의 fetchBoards데이터를 가져와 생성된것과 합쳐준 배열로 리턴한다.


캐시를 직접 수정하는방법이 유의미할때도 무의미할때도 있다.

무한스크롤을 포함한 대부분 => 유의미

게시판 목록에 대해서는 => 무의미

게시판 목록에대해서, 즉, 페이지네이션으로 클릭해 페이지가 넘어가는 방법에 대해서는 목록이 10개씩 보여야하는데 캐시를 수정하는 방법을 사용하면 수정하면 삭제하면 삭제하는대로, 등록하면 등록하는대로 개수가 늘거나 줄어들어 10개보다 많거나 적어지기에 refetch를 사용하는것이낫다.

그러면 무한스크롤, 게시글 수정,댓글무한스크롤과 수정삭제 등에 사용해보면좋을것같다

0개의 댓글