패스트캠퍼스 The Red
🔎 강의 전 사전 지식
Query(쿼리)
란?
- 데이터베이스에게 특정한 데이터를 보여달라는 클라이언트(사용자)의 요청을 말한다.
1) 구글이나 네이버와 같은 검색창에 '파이썬 기초 강의'라는 검색어를 친다
2) 파이썬에 대한 정보들이 나온다
3) 이 정보들은 모두 서버에 저장되어 있던 데이터베이스에서 온 정보들이다.
4) 이 과정에서 내가 (클라이언트에서) '파이썬 기초 강의'에 대한 데이터를 달라는 쿼리를 주었고
5) 서버가 이에 응답해 데이터베이스에 있는 데이터를 보여준 것이다.- 쿼리문을 작성한다 : '데이터베이스에서 원하는 정보를 가져오는 코드를 작성한다' 정도로 이해하면 된다. 쿼리문을 잘 작성한다는 것은 데이터베이스에서 필요한 데이터에 빠르게 접근하고, 데이터를 능숙하게 핸들링한다는 말로도 볼 수 있다.
출처 - 쿼리란 무엇인가?
Mock API를 바탕으로 실제로 데이터와 컴포넌트와 연동시키는 다양한 방법
- useState 활용해 fetch하기
- Axios를 사용해서 데이터 가져오기
- 클래스형 컴포넌트 didMount 시점에 데이터를 가져와서 가져온 데이터를 렌더링시키기
- useQuery
강사가 선호하는 아키텍처 방식
- React : View 레이어
- React-Query : Model 레이어
- BFF(API 서버) : Model 레이어
- Hook : Controller 레이어
React-Query
- 서버의 값을 클라이언트에 가져오거나, 캐싱, 값 업데이트, 에러 핸들링 등 비동기 과정을 더욱 편하게 하는데 사용
💎 참고자료
react-query 설치하기 : npm i react-query --save-dev
QueryClient, QueryClientProvider, useQuery import 후 컴포넌트 감싸기
pages/_app.tsx
import '../styles/globals.css'
import type { AppProps } from 'next/app'
import { QueryClient, QueryClientProvider } from 'react-query'
const queryClient: any = new QueryClient()
function MyApp({ Component, pageProps }: AppProps) {
return (
<QueryClientProvider client={queryClient}>
<Component {...pageProps} />
</QueryClientProvider>
)
}
export default MyApp
- LectureList에서 useQuery로 데이터 가져오기
- fetch로 lectures api 호출해오기
components/lecture/LectureList.tsx
import React from "react";
import LectureItem from './LectureItem';
import { useQuery } from 'react-query'
const LectureList = (): JSX.Element => {
const { isLoading, data } = useQuery('lecture_list', () => {
return fetch('/api/lectures').then((res) => {
return res.json();
}).then((res) => {
return res;
})
})
console.log(data);
return (
<div>
<LectureItem />
</div>
)
}
export default LectureList;
- 로딩 예외 처리
- lectureItems : lectureList data에서 map을 돌려서 각각의 컴포넌트를 만들어준다. (강의목록)
- lecture key, prop를 넣고 lectureItems로 return
components/lecture/LectureList.tsx
...
if (isLoading){
return <div>Loading ...</div>
}
const lectureItems = data.lectureList.map((lecture: Lecture)=>{
return (
<LectureItem key={lecture.id} lecture={lecture} />
)
})
return (
<div>
{ lectureItems }
</div>
)
}
...
- LectureItem에서 TypeScript interface 만들기 (data type)
- interface가 한 tsx 파일 안에 너무 많아지면 관리가 어려우므로 별도로 interface 디렉토리를 만들어서 관련 내용 이동시킨 후 export해간다 (interface/lecture.ts)
interface/Lecture.ts
export interface Category {
id: number,
name: string,
}
export interface Tag {
id: number,
name: string,
}
export interface Lecture {
id: number,
category: Category,
title: string,
tags: [Tag],
description: string,
thumb: string,
isHot: boolean,
isNew: boolean,
}
- LectureTags 컴포넌트 생성 (태그 렌더링에 신경쓰도록 관심사의 분리, 배열로 받아서 노출 시킴)
- LectureTags 배열로 받아서 데이터를 노출 시켜주는 것이므로 컴포넌트 디렉토리 하위에 별도의 tags 디렉토리 생성 후
- LectureTags.tsx 생성 -> API를 연동하다보면 그때그때 컴포넌트를 분리해야하는 일이 발생함
components/tags/LectureTags.tsx
import React from "react";
import { Tag } from "../../interface/Lecture"
interface Props {
tagListData: [Tag]
}
const LectureTags = ({ tagListData } : Props): JSX.Element => {
const tagList = tagListData.map((tag) => {
return (<span key={tag.id}>{tag.name}</span>)
})
return (
<div>{ tagList }</div>
);
}
export default LectureTags;
- LectureItem에 생성한 LectureTags 넣어주기
components/lecture/LectureItem.tsx
import React from "react";
import { Lecture } from "interface/Lecture";
import LectureTags from '../tags/LectureTags';
// 내부에서만 사용하는 interface
interface Props {
lecture: Lecture;
}
const LectureItem = ({ lecture }: Props): JSX.Element => {
return (
<div>
<img src={lecture.thumb} alt="초격차 패키지" />
<LectureTags tagListData={lecture.tags} />
<h3>{lecture.title}</h3>
<p>{lecture.description}</p>
</div>
)
}
export default LectureItem;
LectureGroup에서의 문제점 : 단일 case에서만 처리할 수 있다
- LectureList에서 데이터를 가져오고 있긴 하지만, LectureList 컴포넌트를 보기 전에는 어디서 데이터가 오는지 알 수 없다. API, Query까지 봐야만 알 수 있음
- 데이터 가공해가는 과정이 길다
- 내부에 두는 게 일관성있는지 vs 별도로 로직을 빼는 게 나은지
- 컴포넌트 내부에서 useQuery를 별도로 빼내고 이 useQuery Hook을 외부에서 가져오는 방식으로 변경하기
- hooks/ 디렉토리 생성 후 useLectures.ts 생성
- 기존에 LectureList에 만든 useQuery를 가져와서 return, export
hooks/useLectures.ts
import { useQuery } from 'react-query';
const useLectures = (categories: string) => {
return useQuery('lecture_list', () => {
return fetch('/api/lectures').then((res) => {
return res.json();
}).then((res) => {
return res;
})
})
}
export default useLectures;
- LectureList에 import
components/lecture/LectureList.tsx
import useLectures from '../../hooks/useLectures'
const LectureList = (): JSX.Element => {
const { isLoading, data } = useLectures();
...
}
lectures라는 데이터를 가져오는 레이어는 모두 useLecutes에서 진행하게 된다
Custom Hooks으로 나누는 게 어떤 의의가 있는지?
- Custom Hooks을 나누게 되면 이런
API의 endpoint
가 관리될다는 측면을 고려할 수 있다- 이전 강의에서 만든 mock API인 lectures만 만들었는데, 특정 옵션에 따라 강의 목록을 노출 시켜야 하는 경우 별도로 인지하고 있음 (GET)
- 예 : Custom Hooks에 Categories에 따라서 뭔가를 처리하는 로직을 수행시켜야 할 경우 활용할 수 있다.
- 카테고리 분기처리
hooks/useLectures.ts
const useLectures = (categories: string) => {
if (categories) {
console.log('카테고리에 따른 분기를 처리해야 합니다.')
}
return useQuery('lecture_list', () => {
return fetch(`/api/lectures?categories=${categories}`).then((res) =>
return res.json();
}).then((res) => {
return res;
})
})
}
components/lecture/LectureList.tsx
...
const LectureList = (): JSX.Element => {
const { isLoading, data } = useLectures('Programming');
...
}
...
API로 데이터를 내려줄 때 요청된 카테고리가 어떤 것인지에 따라 응답 코드에 데이터를 내려주게 할 수 있다. (mock API이므로 더미데이터를 만들어서)
- 카테고리가 Programming인 데이터 (lecturesData)
- 카테고리가 DataScience인 데이터 (lecturesDataScience)
pages/api/lectures.ts
...
export default function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
if(req.query.categories === 'Programming'){
//console.log(req.query.categories)
res.status(200).json(lecturesData);
}
if(req.query.categories === 'DataScience'){
res.status(200).json(lecturesDataScience);
}
res.status(200).json(lecturesData)
}
- API 서버를 가지고 있다면 BE 개발자의 도움이 당연히 필요하겠지만, BE 개발자가 API를 만들기 전에 예외 case를 만들거나 query를 날려보거나 인증 관련된 cookie를 제대로 생성되는지 등등을 테스트해볼 수 있어 유용하게 활용할 수 있다.
- Custom Hooks으로 분리해두면 query를 변경한 것이 그대로 반영되기 때문에 여러 군데에서 변경된 query를 사용할 수 있다는 장점이 있다.
- Custom Hooks만 봐도 지금 어떤 Parameter를 보내고 있고, 어디에 어떤 query를 수행해야하는지 한눈에 볼 수 있어 로직을 이해하는데 유용하다.
- mock API할지라도 실제 API화 할 수 있으므로 설계 단계에서부터 어떤 parameter를 보낼 건지 어떤 header를 보낼 건지 cookie에는 어떤 정보가 들어가야하는지 등등 미리 판단하고 진행하할 수 있다.
Result
useQuery & custom Hooks를 통해 카테고리 분기로 불러온 데이터
API에 더미데이터로 저장
(CSS Styling은 생략)
Next.js 구현 흐름 파악하기
1. 함께 일하는 동료들과의 협의가 필요한 사항 준비
1) 패키지 설정 및 Next.js 설치
2) 개발환경 설정 : Testing Library, ESLint, README.md2. 데이터를 어떻게 할까
1) 데이터 플로우 그리기
2) 유저 시나리오 작성하기3. 렌더링
1) 페이지 생성하기
2) 컴포넌트 생성/설계
3) 테스트 코드 작성과정을 통해 검증4. 데이터 설계
1) 기준 : 유저 시나리오에 맞게 데이터를 잘 설계하고 있는가?
2) mock API 생성 : 화면만 봐서는 놓칠 수 있는 케이스 파악
3) useQuery : 데이터 연동 체크
4) custom Hooks : 관련된 로직을 재사용할 수 있도록 코드 분리 작업💎 생각해봐야 할 점
- 컴포넌트를 어떻게 추가할 때는 테스트 케이스 작성과 병행
- 엣지 포인트, 엣지 케이스 설계
- mock API 설계를 통해 request/response를 체크하기
- custom hooks를 통해 실제로 관련된 로직을 사용하는 모든 곳에서 일관된 코드가 나올 수 있도록 할 수 있게 하기
💬 조금 어려운 부분도 있어 1배속으로 천천히 따라하며 조은님의 The Red 완강😀🤟