수업 중 기억하고 싶은 내용
1. 정적라우팅
NextJS에서 기본적으로 라우팅을 지원한다.
useRouter() 훅을 사용하면 push() 메서드를 통해 경로를 이동할 수 있다.
이전에 쓰던 <Link />와 차이?
Link태그는 단순히 페이지 간 이동에 더 중점적이고, useRouter()를 사용하면 특정 기능 실행 후 이동한다. (기능이나 비동기 처리 등을 완료하고 이동하는 조건부 제어)
그럼 useRouter() 어떻게 사용하는데?
1. 일단 import 해줘야겠지?
// 리액트 17 이하
import { useRouter } from "next/router";
// 현재
import { useRouter } from "next/navigation";
현재 사용하는 방식처럼 next의 navigation에서 useRouter를 가지고 오면 된다.
2. 불러온 useRouter() 사용하기
const router = useRouter();
function 기능 () {
router.push("이동할 페이지 주소, 이때 주소는 src하단부터 작성");
};
예제 코드
"use client";
import { useRouter } from "next/navigation";
const StaticRoutingPage = () => {
const 라우터 = useRouter();
const onClickSubmit = () => {
// 1. 게시글 등록
// ...
// 2. 등록된 페이지로 이동하기
라우터.push("/section07/07-01-static-routing-moved");
};
return (
<>
<button onClick={onClickSubmit}>게시글 등록하기</button>
</>
);
};
export default StaticRoutingPage;
2. 게시글 조회
등록은 동기방식인데 조회는 왜 비동기방식일까?
이 이유는 둘의 목적에서 찾을 수 있다.
등록의 경우, 등록을 해야 다음 과정으로 넘어갈 수 있기 때문이다.
예를 들어, 회원가입 과정에서 프로필을 등록했는데 비동기 방식으로 등록하고 로그인 페이지로 route 시켰다고 가정해 보자.
사용자가 로그인 창에 조금 전 가입한 정보를 입력해도 아직 DB에 저장되지 않았기 때문에 로그인 정보를 찾을 수 없다고 나올 것이다.
이 경우, 가입한 데이터를 DB에 저장시키고 난 뒤에 로그인 페이지로 이동되야한다.
따라서 동기방식을 이용해 데이터를 저장시키고 그다음에 이동할 수 있도록 해줘야 한다.
하지만 조회의 경우에서는 화면에 데이터를 보여주는 데 있어 비동기 방식이 더 사용자 친화적일 수 있다.
조회를 하는 주목적은 화면 단에 데이터를 뿌려주기(보여주기) 위해서인 경우가 많기 때문이다.
그렇다면 왜 화면에 보여주는 경우에는 비동기 방식이 더 효율적인 것일까?
쿠팡의 상품페이지를 보고 있다고 생각해 보자.
사용자들은 상품페이지에서 상품을 골라 상세페이지로 이동할 것이다.
상세페이지에 접속했을 때, 기본적인 구조(ex. 메뉴, 사진 위치, 설명 위치 등)는 데이터가 없이 먼저 보이고 있어도 상관없다.
오히려 위와 같이 해야 사용자들이 데이터를 불러오기까지 기다리는 과정에 대한 지루함을 줄일 수 있다.
화면에 보여주는 역할뿐이지, 회원가입의 사례처럼 가입이 완료되지 않으면 로그인할 수 없는, 그런 연관적인 구조가 아니라서 비동기 방식으로 구현 가능하다.
GraphQL로 게시글 조회하기
그래프큐엘로 조회 작업을 할 때는 useQuery()를 사용한다.
1. useQuery() 사용하고 싶으면 import 우선 해야겠지?
import { useQuery } from "@apollo/client";
2. 바로 예제코드로!
"use client";
import { gql, useQuery } from "@apollo/client";
const FETCH_BOARD = gql`
query {
fetchBoard(number: 2) {
writer
title
contents
}
}
`;
const StaticRoutingMovedPage = () => {
// data => fetchBoard{} 형식
const { data } = useQuery(FETCH_BOARD);
console.log(data);
return (
<>
<div>상세페이지 2 이동이 완료되었습니다.</div>
<div>작성자 : {data.fetchBoard.writer}</div>
<div>제목 : {data.fetchBoard.title}</div>
<div>내용 : {data.fetchBoard.contents}</div>
</>
);
};
export default StaticRoutingMovedPage;
+ 실습할 때:
분명 경로 맞게 입력했는데 계속 404 발생 -> 폴더명 뒤에 나도 모르는 공백 한 칸 있었다...
지우니 해결.
+ fetchBoard로 입력해야 하는데 그냥 내 맘대로 fetchboard라고 작성. 콘솔 찍어보고 수정하니 해결.
콘솔에 값이 두 번 찍혀요!
이 사진을 다시 보자.
console이 두 번 찍힌 것을 볼 수 있다.
(StaticRoutingMovedPage) 함수가 두 번 실행되는 것이다.
왜 두 번 실행될까?
리액트의 개발 모드 특성에 있다.
리액트에는 StrictMode라고 검증을 빡시게 해주는 친구가 있다.
이 친구가 화면을 두 번 그려주기 때문에 두번 실행된다.
따라서 next.config.mjs 파일에서 아래와 같이 작성해 주면 된다.
/** @type {import('next').NextConfig} */
const nextConfig = {
// 리액트 스트릭트 모드 끄기
reactStrictMode: false,
};
export default nextConfig;
처음 화면을 그릴 때에는 데이터를 아직 불러오지 못한 상태이기 때문에 undefined가 나온다.
데이터가 불러와지면 그때 다시 화면을 그려주게 된다.
그럼 데이터가 불러와지기 전까지는 화면에 그려줄 수 있는 값이 없나?
맞다 그래서 에러가 발생하게 된다.
이런 에러를 방지하기 위해서 사용할 수 있는 방법이 있다.
1. 삼항 연산자를 사용하는 방식
data? data.fetchBoard: undefined;
2. && 연산자를 사용하는 방식
data && data.fetchBoard.writer;
3. Optional-Chaning
data?.fetchBoard.writer;
세 방식 중 Optional-Chaning을 권장하는 이유가 뭐야?
세 방법 모두 앞의 조건이 참이면 뒤의 내용을 보여주는 방식의 코드이다.
하. 지. 만 우리가 옵셔널 체이닝을 사용하는 이유는 안정성과 가독성 때문이다.
Nullish-Coalescing(??)? 이건 뭐야?
앞의 조건이 거짓이면 뒤에 내용을 보여주는 방식으로 코드를 작성할 수도 있는데, 이때 앞의 값이 그냥 거짓이 아닌 빈 값, 즉 Null이나 undefined인 경우를 말한다.
받아오는 값이 비어있으면 우항에 있는 값을 보여주는 것이다.
data ?? data.fetchBoard.writer;
3. 동적 라우팅
동적 라우팅을 하려면 useParams()가 필요하다고?
1. useParams()를 사용해 보자!
import { useParams } from "next/navigation";
2. useParams() 사용하기
과거에는 router를 이용해서 params와 같은 역할을 할 수 있었으나, 지금은 불가능하다.
// 구버전 방식
const router = useRouter();
router.push("");
router.query.pageNum;
// 신버전 방식
const params = useParams();
예제코드
"use client";
import { gql, useQuery } from "@apollo/client";
import { useParams } from "next/navigation";
const FETCH_BOARD = gql`
query fetchBoard($number: Int) {
fetchBoard(number: $number) {
writer
title
contents
}
}
`;
const StaticRoutingMovedPage = () => {
const params = useParams();
const { data } = useQuery(FETCH_BOARD, {
variables: {
number: Number(params.number),
},
});
console.log(data);
return (
<>
<div>상세페이지 {params.number} 이동이 완료되었습니다.</div>
<div>작성자 : {data && data.fetchBoard.writer}</div>
<div>제목 : {data ? data.fetchBoard.title : ""}</div>
<div>내용 : {data?.fetchBoard.contents}</div>
</>
);
};
export default StaticRoutingMovedPage;
4. 게시글 등록부터 조회까지
폴더구조
게시글 등록
"use client";
import { ChangeEvent, useState } from "react";
import { useMutation, gql } from "@apollo/client";
import { useRouter } from "next/navigation";
const 나의그래프큐엘세팅 = gql`
mutation createBoard($myWriter: String, $myTitle: String, $myContents: String) {
createBoard(writer: $myWriter, title: $myTitle, contents: $myContents) {
_id
number
message
}
}
`;
const StaticRoutingPage = () => {
const router = useRouter();
const [writer, setWriter] = useState("");
const [title, setTitle] = useState("");
const [contents, setContents] = useState("");
// 그래프큐엘
const [나의함수] = useMutation(나의그래프큐엘세팅);
// useState 힘수
const onChangeWriter = (event: ChangeEvent<HTMLInputElement>) => {
setWriter(event.target.value);
};
const onChangeTitle = (event: ChangeEvent<HTMLInputElement>) => {
setTitle(event.target.value);
};
const onChangeContents = (event: ChangeEvent<HTMLInputElement>) => {
setContents(event.target.value);
};
const onClickSubmit = async () => {
// try에 있는 내용을 시도하다가 실패하면, 그 아랫줄들을 무시하고 catch로 넘어감
try {
const result = await 나의함수({
variables: {
myWriter: writer,
myTitle: title,
myContents: contents,
},
});
console.log(result);
// 여기서 옵셔널 체이닝이 필요없는 이유: 등록을 하면서 발생하는 기다리는 시간동안 불러올 수 있기 때문,
// 동기 방식이라 등록하고 불러오는 과정을 마치고 다음 단계가 진행되기 때문이다.
console.log(result.data.createBoard.number);
alert("게시글 등록에 성공하였습니다.");
const url = result.data.createBoard.number;
router.push(`/section07/07-04-dynamic-routing-board-mutation-moved/${url}`);
} catch (error) {
console.log(error);
alert(`게시글 등록에 실패하였습니다. \n 에러코드: ${error}`);
}
};
return (
<>
작성자: <input type="text" onChange={onChangeWriter} /> <br />
제목: <input type="text" onChange={onChangeTitle} /> <br />
내용: <input type="text" onChange={onChangeContents} /> <br />
<button onClick={onClickSubmit}>GraphQL 동기 요청하기</button>
</>
);
};
export default StaticRoutingPage;
게시글 조회
"use client";
import { gql, useQuery } from "@apollo/client";
import { useParams } from "next/navigation";
const FETCH_BOARD = gql`
query fetchBoard($number: Int) {
fetchBoard(number: $number) {
writer
title
contents
}
}
`;
const StaticRoutingMovedPage = () => {
const params = useParams();
const { data } = useQuery(FETCH_BOARD, {
variables: {
number: Number(params.number),
},
});
console.log(data);
return (
<>
<div>상세페이지 {params.number} 이동이 완료되었습니다.</div>
{/* && 연산자 */}
<div>작성자 : {data && data.fetchBoard.writer}</div>
{/* 삼항 연산자 */}
<div>제목 : {data ? data.fetchBoard.title : ""}</div>
{/* 옵셔널 체이닝 */}
<div>내용 : {data?.fetchBoard.contents}</div>
</>
);
};
export default StaticRoutingMovedPage;
try-catch 구문을 사용해, 데이터가 정상적으로 등록되지 않은 경우에 대한 처리도 진행해 줬다.
5. 추가 내용
useQuery()에는 data 뿐만 아니라 loading, error 상태도 제공한다.
const StaticRoutingMovedPage = () => {
const { data, loading, error } = useQuery(FETCH_BOARD);
if (loading) return <p>Loading...</p>; // 로딩 중 메시지
if (error) return <p>Error occurred!</p>; // 에러 처리
return (
<>
<div>상세페이지 2 이동이 완료되었습니다.</div>
<div>작성자 : {data?.fetchBoard?.writer}</div>
<div>제목 : {data?.fetchBoard?.title}</div>
<div>내용 : {data?.fetchBoard?.contents}</div>
</>
);
};
둘을 적절히 사용하면 로딩 중, 에러 시 다른 UI를 보여줄 수도 있다.
(24.10.16 +)
graphql에서 제공하는 useQuery나 useMutation에서 받아오는 반환값은 React의 state처럼 동작한다.
이 값들은 컴포넌트가 렌더링 될 때마다 최신 상태로 업데이트되기 때문이다.
다만, useQuery는 컴포넌트 리렌더 시 자동으로 실행되지만, useMutation의 경우 컴포넌트 렌더링 시 자동 실행이 아닌, 명시적 호출이 필요하다.
명시적 호출이란, 사용자 동작, 즉 이벤트가 발생해야 호출(실행)된다는 것이다.
짧은 회고
정적라우팅과 동적라우팅에 대해 공부했다.
저번 NextJS 프로젝트할 때는 라우팅 방법을 잘 몰랐고, Link 태그만 주로 사용하면서 했었는데, 이번에 다시 공부하니까 이래서 그랬구나 하고 이해가 됐다.
'💪 Study > 💻 Codecamp 13기' 카테고리의 다른 글
[241031] 메인캠프 Day44 - 1 (유틸리티 타입) (1) | 2024.10.31 |
---|---|
[240902] CSS & JS 마스터 3일차 TIL (스크롤 & 이벤트 버블링) (0) | 2024.09.02 |
[240826] 프리캠프 1일차 TIL (0) | 2024.08.29 |