라우팅 & 비동기 통신과 리렌더링

degull·2023년 5월 23일
post-thumbnail

라우터 객체와 라우팅

라우터(router)객체란 페이지 이동과 관련된 기능을 가지고 있는 객체
이 객체를 사용해 A페이지에서 B페이지로 이동할 때, 'B페이지로 라우팅'이라고 한다.

기초 라우터 객체 기능

  • static-routing-board
import { useRouter } from 'next/router';
import React from 'react';

export default function StaticRoutingPage(){

   const router = useRouter()

   const onClickMove1 = () =>{
      router.push("/static-router-board/1")
   }
   const onClickMove2 = () =>{
      router.push("/static-router-board/2")
   }
   const onClickMove3 = () =>{
      router.push("/static-router-board/3")
   }

   return (
      <>
         <button onClick={onClickMove1}>1번 게시글 이동하기</button>
         <button onClick={onClickMove2}>2번 게시글 이동하기</button>
         <button onClick={onClickMove3}>3번 게시글 이동하기</button>
      </>
   );
};

  • static-routed-board/1
import React from 'react';

export default function StaticRoutedPage(){
   return (
      <>
         <div>1번 게시글로 이동 완료</div>
      </>
   );
};

  • static-routed-board/2
import React from 'react';

export default function StaticRoutedPage(){
   return (
      <>
         <div>2번 게시글로 이동 완료</div>
      </>
   );
};

  • static-routed-board/3
import React from 'react';

export default function StaticRoutedPage(){
   return (
      <>
         <div>3번 게시글로 이동 완료</div>
      </>
   );
};

정적라우팅과 동적라우팅

정적라우팅

/board 페이지는 누가 언제 접속해도 항상 board 페이지가 접속된다.
이렇게 페이지로 이동하는 것을 "정적 라우팅"이라고 한다.

동적라우팅

게시판 상세보기와 같은 경우, 글 번호에 따라서 주소가 변경된다.
이러한 경우, 게시글이 100개 1000개가 넘어가게 되면 글 번호에 따라 페이지를 만들어 정적라우팅을 해주기 어렵기 때문에 '동적라우팅'을 사용한다.

  • 동적 라우팅 예시
    /board/1 => 1번 게시글 상세보기 페이지
    /board/2 => 2번 게시글 상세보기 페이지
    /board/3 => 3번 게시글 상세보기 페이지
    /board/4 => 4번 게시글 상세보기 페이지

추가학습주제
라우터 객체의 push와 replace 의 차이를 아시나요? 사용 예시를 하나씩 말해보세요.
라우터 객체의 pathname과 asPath는 무엇이 다른가요?
React에도 Next 처럼 Router가 있습니다.
React와 Next의 Router는 분명히 다릅니다. 어떤 차이가 있는지 설명해 보세요.

비동기 통신과 조건부 렌더링

javascript는 작성된 코드가 상단에서부터 순서대로 실행되기 때문에 데이터를 요청하고 응답을 받아오는 동안 화면에 그려질 데이터의 내용이 undefined이므로 첫 화면이 그려지는 시기에 데이터를 불러오면 에러가 발생한다.

이 부분이 효율적으로 실행되기 위해서 화면을 미리 그려놓고 데이터를 그려주기 위해 조건부렌더링을 사용한다.

에러발생 코드

  • static-routing-board-query
import { useRouter } from 'next/router';
import React from 'react';



export default function StaticRoutingPage(){

   const router = useRouter()

   const onClickMove1 = () =>{
      router.push("/0523-static-routed-board-query/1")
   }
   const onClickMove2 = () =>{
      router.push("/0523-static-routed-board-query/2")
   }
   const onClickMove3 = () =>{
      router.push("/0523-static-routed-board-query/3")
   }

   return (
      <>
         <button onClick={onClickMove1}>1번 게시글 이동하기</button>
         <button onClick={onClickMove2}>2번 게시글 이동하기</button>
         <button onClick={onClickMove3}>3반 게시글 이동하기</button>
      </>
   );
};
  • static-routed-board-query
import { useQuery, gql } from '@apollo/client';
import React from 'react';


// FETCH_BOARD => 조회
const FETCH_BOARD = gql`
   query fetchBoard($number:Int){   
      fetchBoard(number :$number){
         writer
         title
         contents
      }
   }
`

export default function StaticRoutedPage(){
   const {data} = useQuery(FETCH_BOARD, {
      variables : {number : 1}
   })

   console.log(data)

   return (
      <>
         <div>1번 게시글로 이동 완료</div>
         <div>작성자 : {data.fetchBoard.writer}</div>
         <div>제목 : {data.fetchBoard.title}</div>
         <div>내용 : {data.fetchBoard.contents}</div>
      </>
   );
};

🔥🔥실행 시 에러 발생

에러가 발생하는 이유는 data 객체가 null 또는 undefined일 때 fetchBoard.writer, fetchBoard.title, fetchBoard.contents에 접근하려고 하기 때문이다.

Apollo Client의 useQuery 훅은 데이터를 비동기적으로 가져오기 때문에, 데이터가 아직 로딩되지 않았을 때 data 객체는 초기값인 undefined이기 때문에 data 객체가 존재하는지 확인한 후에 접근해야 한다..

해결1

import { useQuery, gql } from '@apollo/client';
import React from 'react';

const FETCH_BOARD = gql`
  query fetchBoard($number: Int) {
    fetchBoard(number: $number) {
      writer
      title
      contents
    }
  }
`;

export default function StaticRoutedPage() {
  const { data } = useQuery(FETCH_BOARD, {
    variables: { number: 1 },
  });

  if (!data) {
    return <div>Loading...</div>;
  }

  console.log(data);

  return (
    <>
      <div>1번 게시글로 이동 완료</div>
      <div>작성자: {data.fetchBoard.writer}</div>
      <div>제목: {data.fetchBoard.title}</div>
      <div>내용: {data.fetchBoard.contents}</div>
    </>
  );
}

---> data가 존재하지 않을 경우, 로딩 중임을 나타내도록 함으로써 사용자에게 데이터가 로딩되는 동안 사용자에게 로딩 상태를 나태낼 수 있다.

해결2

import { useQuery, gql } from '@apollo/client';
import React from 'react';


// FETCH_BOARD => 조회
const FETCH_BOARD = gql`
   query fetchBoard($number:Int){   
      fetchBoard(number :$number){
         writer
         title
         contents
      }
   }
`

export default function StaticRoutedPage(){
   const {data} = useQuery(FETCH_BOARD, {
      variables : {number : 1}
   })

   console.log(data)

   return (
      <>
         <div>1번 게시글로 이동 완료</div>
         <div>작성자 : {data ? data.fetchBoard.writer : "로딩 중.."}</div>
         <div>제목 : {data && data.fetchBoard.title}</div>
         <div>내용 : {data?.fetchBoard.contents}</div>
      </>
   );
};

조건부 렌더링

삼항 연산자

data ? data.fetchProfile : undefined

data는 동기적으로 받아와야하는 데이터이지만 데이터가 오기 전에 이미 return 부분에서 rendering을 해주고 있기 때문에 삼항 연산자를 써서 데이터가 있을 때와 없을 때를 모두 조건을 나눠주었다.

&&연산자

data && data.fetchProfile

&&연산자는 데이터가 없을 경우 자동으로 undefined를 반환한다. 데이터가 없을 때 따로 div를 쓸 필요가 없으며 else부분을 쓸 필요가 없다.

&&연산자는 앞의 값이 참일 경우에만 뒤의 값을 출력했는데, 반대로 앞의 값이 거짓일 때 위의 값을 보여주는 경우도 있다. 이를 "Nullish coalescing"연산자라고 한다.

??연산자는 앞의 값이 빈 값이면 뒤의 값을 보여주며 ||연산자는 앞의 값이 거짓일 경우 뒤의 값을 출력한다.

data ?? data.fetchProfile
data || data.fetchProfile

옵셔널 체이닝 (Optional-Chaining)

optional-chaining이란 기존의 &&연산자를 쓰면서 길어졌던 코드를 간결하게 사용하는 연산자이다.

optional-chaining은 ES2020으로 최신문법이다.

data?.fetchProfile

?연산자 앞 객체의 참조가 undefined||null 이라면 undefined를 리턴한다.

삼항연산자, &&연산자와 같은 기능을 하지만 가독성은 훨씬 좋다.

{data ? data.fetchBoardsOfTheBest.map() : <div></div>}
{data && data.fetchBoardsOfTheBest.map()}
{data ?.fetchBoardsOfTheBest.map()

동적 라우팅

[aaa]는 변수로 [aaa]안에 있는 index.js로 이동하도록 한다.
boards/____ 어느 곳으로든 이동 가능


http://localhost:3000/boards/1

http://localhost:3000/boards/[aaa]

💡변수 꺼내는 방법

const router = useRouter()
	router.query = {
    	aaa : 1
    	}

실습

  • dynamic-routing-board-query
import React from 'react';
import { useState } from 'react';
import { useRouter } from 'next/router';


export default function StaticRoutingPage(){

   const router = useRouter()

   const onClickMove1 = () => {
      router.push("/dynamic-routed-board-query/1")
   }
   const onClickMove2 = () => {
      router.push("/dynamic-routed-board-query/2")
   }
   const onClickMove3 = () => {
      router.push("/dynamic-routed-board-query/3")
   }
   const onClickMove100 = () => {
      router.push("/dynamic-routed-board-query/100")
   }

   return (
      <>
         <button onClick={onClickMove1}>1번 게시글로 이동</button><br/>
         <button onClick={onClickMove2}>2번 게시글로 이동</button><br/>
         <button onClick={onClickMove3}>3번 게시글로 이동</button><br/>
         <button onClick={onClickMove100}>100번 게시글로 이동</button><br/>
      </>
   );
};
  • dynamic-routed-board-query
import React from 'react';
import {useQuery, gql} from '@apollo/client'
import { useRouter } from 'next/router';

const FETCH_BOARD = gql`
   query fetchBoard($number:Int){
      fetchBoard(number:$number){
         writer
         title
         contents
      }
   }
`

export default function StaticRoutedPage(){

   const router = useRouter()
   console.log(router)
   console.log(router.query.qqq)


   const {data} = useQuery(FETCH_BOARD, {
      variables : {number : Number(router.query.qqq)}
   })

   console.log("========")
   console.log(data)
   console.log("========")

   return (
      <>
         <div>{router.query.qqq}번 게시글로 이동 완료</div>
         <div>작성자 : {data ? data.fetchBoard.writer : "로딩 중.."}</div>
         <div>제목 : {data && data.fetchBoard.title}</div>
         <div>내용 : {data?.fetchBoard.contents}</div>
      </>
   );
};

router.query.qqq를 통해 page 이동

profile
그래도 해야지

0개의 댓글