[패스트캠퍼스] 조은의 프론트엔드 개발 실무 가이드 : 요구사항 분석과 적정 기술 (4-3)

productuidev·2022년 7월 10일
1

FE Study

목록 보기
41/67
post-thumbnail

조은의 프론트엔드 개발 실무 가이드 : 요구사항 분석과 적정 기술 (4-3)

패스트캠퍼스 The Red

Next 기반 애플리케이션 개발하기

CH04_06. UseQuery로 데이터 가져오기

🔎 강의 전 사전 지식

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;

CH04_07. Custom Hooks로 Hooks Layer 빼내기

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.md

2. 데이터를 어떻게 할까

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 완강😀🤟

profile
필요한 내용을 공부하고 저장합니다.

0개의 댓글