✍️ Youtube Api를 사용하던 중, Youtube Api는 사용 횟수에 제한을 두고 있으므로 개발모드에서는 mock데이터를 사용하고, 필요할 때에는 실제 Api를 사용하는 것이 추천된다고 들었다. 😯
물론 이전에도 백엔드 작업이 다 이루어지기 전까지는 mock데이터(json)를 통해서 미리 프론트 코드를 작성해두곤 했었는데, 이때 목데이터와 관련된 코드, 그리고 실제 데이터를 받아오는 코드에서 어디를 주석처리하냐에 따라서 그것과 관계된 코드도 수정해야 했었다. 당장은 실제 통신과 목데이터 통신 처리를 잘 구분해서 개발 하더라도 나중에 다시 코드를 들여다보면 어느부분을 어떻게 수정 해야 전환되는지 조금 헷갈릴 때도 많았던 것 같다. 😓
이때 추천되는 방식중 하나로, class를 이용한 방법을 학습하게 되었는데 이것에 대한 부분을 메모해두려고 한다!
📌 아래 코드는 React를 사용하여 YouTube API와 상호 작용하는 웹 애플리케이션을 만드는 코드이다.
이 코드는 클래스 기반 설계를 활용하여 데이터 가져오기를 관리하며, 가짜 데이터(FakeYoutubeClient)와 실제 YouTube API(YoutubeClient) 간의 전환을 용이하게 해준다.
ApiContext
export const YoutubeApiContext = createContext<any>({});
// 목데이터 사용시 주석해제
// const client = new FakeYoutube();
// 실제 데이터 사용시 주석해제
const client = new YoutubeClient();
const youtube = new Youtube(client); // 필요 client만 교체
export function YoutubeApiProvider({ children }) {
return (
<YoutubeApiContext.Provider value={{ youtube }}>
{children}
</YoutubeApiContext.Provider>
);
}
export function useYoutubeApi() {
return useContext(YoutubeApiContext);
}
YoutubeApiProvider
컴포넌트는 React Context를 생성하여 애플리케이션 전체에 걸쳐 사용할 수 있는 Youtube 인스턴스를 제공한다.YoutubeApiContext
를 사용하여, youtube 객체를 Context에 넣어준다. 이 객체는 Youtube 클래스(를 이용해서 만든)인스턴스이다.useYoutubeApi
훅은 이 Context를 사용하여 컴포넌트가 Youtube 인스턴스에 접근할 수 있게 해준다.YouTube API와 상호 작용을 관리하는 클래스
export default class Youtube {
private apiClient: YoutubeClientInterface;
constructor(apiClient: YoutubeClientInterface) {
this.apiClient = apiClient;
}
async playlist(id: string) {
return this.apiClient
.playlists({ params: { part: 'snippet', channelId: id } })
.then((res: any) => res.data.items);
}
async search(keyword: KeywordType) {
return keyword ? this.#searchByKeyword(keyword) : this.#mostPopular();
}
async #searchByKeyword(keyword: KeywordType) {
return this.apiClient
.search({
params: {
...
q: keyword,
},
})
.then((res: any) =>
res.data.items.map((item: any) => ({ ...item, id: item.id.videoId }))
);
}
async #mostPopular() {
return this.apiClient
.videos({
params: {
...
},
})
.then((res: any) => res.data.items);
}
}
👉 YoutubeClientInterface
export interface YoutubeClientInterface {
search(params: any): Promise<any>;
videos(params: any): Promise<any>;
channels(params: any): Promise<any>;
playlists(params: any): Promise<any>;
}
apiClient
는 YoutubeClientInterface 타입의 객체를 저장하는 멤버 변수이다. YouTube API와 통신하는데 사용되는 클라이언트를 참조한다.constructor(apiClient: YoutubeClientInterface)
는 클래스의 인스턴스를 생성할 때 외부에서 apiClient 객체를 주입받는다.async
함수로 구현되어 있으며, apiClient와의 통신 결과를 기다린 후 처리한다.#searchByKeyword
와 #mostPopular
메소드는 클래스 외부에서는 호출할 수 없는 private 메소드이다. 이는 내부 구현을 캡슐화하고 데이터 은닉을 강화하며 외부 인터페이스를 깔끔하게 유지한다.실제 Api통신을 위한
export default class YoutubeClient {
private httpClient: AxiosInstance;
constructor() {
this.httpClient = axios.create({
baseURL: 'https://youtube.googleapis.com/youtube/v3',
params: { key: process.env.REACT_APP_YOUTUBE_API_KEY },
});
}
async search(params: AxiosRequestConfig) {
return this.httpClient.get('search', params);
}
async videos(params: AxiosRequestConfig) {
return this.httpClient.get('videos', params);
}
async playlists(params: AxiosRequestConfig) {
return this.httpClient.get('playlists', params);
}
}
httpClient
는 AxiosInstance
타입의 객체를 저장하는 멤버 변수이다.가짜 Api통신을 위한 Mock
export default class FakeYoutubeClient {
async search() {
return axios.get('/videos/search.json');
}
async videos() {
return axios.get('/videos/popular.json');
}
async playlists() {
return axios.get('/videos/playlist.json');
}
}
실제로 데이터를 불러오기(사용하기)
export default function Ex() {
const { youtube } = useYoutubeApi();
const { data: 데이터이름 } = useQuery({
queryKey: ['키이름'],
queryFn: () => youtube.사용메서드(필요한param),
staleTime: 1000 * 60 * 5,
});
console.log('받아온 데이터 : ', 데이터이름)
return (
<>
...
</>
);
}
✍️ 코드흐름 알기
→ useQuery로 데이터를 불러온다
데이터를 불러올 컴포넌트는 useQuery를 사용해 비동기 데이터를 불러온다.
이때 useQuery의 queryFn으로 youtube.search 함수를 지정한다. 이 함수는 사용자가 입력한 키워드를 인자로 받아 처리한다.
→ Youtube 클래스의 search 메소드를 실행한다
youtube.search 함수는 Youtube 클래스의 인스턴스 메소드이다.
이 메소드는 주어진 키워드에 따라 this.#searchByKeyword 또는 this.#mostPopular를 내부적으로 호출한다.
→ Youtube 클래스의 Private 메소드를 실행한다
this.#searchByKeyword 또는 this.#mostPopular 메소드는 클래스의 private 메소드로, 외부에서는 접근할 수 없다.
이 메소드들은 this.apiClient를 사용해 실제 YouTube API에 대한 요청을 수행한다. 여기서 this.apiClient는 Youtube 클래스의 생성자를 통해 주입된 YoutubeClient 또는 FakeYoutubeClient의 인스턴스이다.
→ YoutubeClient 또는 FakeYoutubeClient의 search 메소드를 호출한다.
this.apiClient.search는 YoutubeClient 또는 FakeYoutubeClient 클래스 내에 정의된 search 메소드를 호출한다.
YoutubeClient의 search 메소드는 실제 YouTube API와 통신해 데이터를 가져온다. 반면, FakeYoutubeClient의 search 메소드는 가짜 데이터를 반환한다.
→ 결과를 반환받아 화면에 표시한다
search 메소드의 결과는 호출된 컴포넌트로 반환된다.
useQuery는 이 데이터를 관리하며, 로딩 상태나 오류 처리를 수행한 후 결과를 화면에 표시한다.
🤣
YouTube API와 상호 작용을 관리하는 클래스
: 이 클래스는 YoutubeClient 또는 FakeYoutubeClient 인스턴스를 받아 이를 사용하여 실제 API 또는 가짜 데이터를 가져온다.
YoutubeClient와 FakeYoutubeClient
: Youtube 클래스에 의해 사용되는 클라이언트이다. YoutubeClient는 실제 YouTube API와 통신하고, FakeYoutubeClient는 가짜 데이터를 제공한다.
의존성 주입(DI)
: Youtube 클래스는 생성자를 통해 YoutubeClient 또는 FakeYoutubeClient의 인스턴스를 받는다. 이를 통해 테스트 중이거나 개발 중일 때 가짜 데이터를 쉽게 사용할 수 있다.⭐️
React Context
: YoutubeApiContext를 사용하여 애플리케이션 전반에 걸쳐 YouTube 데이터 접근 로직을 공유한다. 이를 통해 컴포넌트 트리 어디서나 YouTube 데이터를 쉽게 접근할 수 있습니다.
React Query 사용
: useQuery를 사용하여 비동기 데이터를 가져오고 캐싱하는 로직을 관리한다. 이는 데이터 로딩, 캐싱, 업데이트를 용이하게 하며, 코드의 복잡성을 줄여 준다.
Private함수 사용
: 클래스 내부에서만 접근 가능한 private 메소드를 사용함으로써, 클래스의 내부 구현을 숨길 수 있다. 이는 클래스의 공개 인터페이스를 깔끔하게 유지하는 데 도움이 된다. Private 메소드는 클래스의 외부에서는 호출되지 않으므로, 클래스 외부에서는 이러한 내부 로직에 대해 신경 쓸 필요가 없다. 이는 캡슐화와 추상화를 강화한다.
📌 동일한 인터페이스를 가진 다양한 구현
: 동일한 인터페이스를 사용하여 다양한 구현을 제공하는 것은 객체 지향 프로그래밍의 핵심 원칙 중 하나라고 한다.😀 이를 통해 사용자는 구현의 세부 사항을 몰라도 동일한 방식으로 다양한 객체를 사용할 수 있게된다.
예를 들어, YoutubeClient와 FakeYoutubeClient는 동일한 메소드(예: search, videos)를 가지지만, 각각 실제 API 호출과 가짜 데이터 반환을 다르게 구현한다. 이러한 접근 방식은 코드의 재사용성을 높이고, 테스트 및 유지보수를 쉽게 만들 수 있다.
유연성
: 클래스를 사용하면 실제 클라이언트와 가짜 클라이언트 간에 쉽게 전환할 수 있다. 이는 테스트 및 개발 단계에서 유용하다.
재사용성 및 유지보수
: 클래스와 컴포넌트를 분리함으로써, 비즈니스 로직과 UI 로직을 분리할 수 있다. 이는 코드의 재사용성과 유지보수를 용이하게 한다.
테스트 용이성
: 가짜 데이터를 사용하여 개발 중 또는 테스트 중에 API 호출 없이도 애플리케이션의 동작을 시뮬레이션할 수 있다.
확장성
: 실제 YouTube API의 변경사항이 있을 때, YoutubeClient 클래스만 업데이트하면 되므로 다른 부분의 코드 수정 없이도 변경사항을 적용할 수 있다.
❓ 그렇다면 일반적으로 이러한 방식을 사용할까? 🧐
👉 이러한 클래스 기반 접근 방식은 매우 일반적이라고 한다. 특히 대규모 애플리케이션 또는 복잡한 비즈니스 로직을 가진 프로젝트에서 이러한 접근 방식은 코드의 가독성, 유지보수성 및 확장성을 크게 향상시킨다. 또한, 테스트 주도 개발(TDD) 및 소프트웨어 개발에서의 좋은 관행을 따르는 데도 유용하다.
캡슐화 및 추상화
: 클래스를 사용하면 관련 데이터와 메소드를 함께 묶어 캡슐화할 수 있다. 이렇게 함으로써 복잡한 로직을 추상화하고 사용자에게 단순한 인터페이스를 제공할 수 있다.
재사용성 및 확장성
: 클래스는 재사용 가능한 코드 블록을 만들어낼 수 있어, 유사한 동작을 하는 다른 부분에 쉽게 적용될 수 있다. 또한, 상속과 같은 객체 지향 프로그래밍(Object-Oriented Programming, OOP)의 특징을 활용하여 기능을 확장할 수 있다.
테스트 용이성
: 클래스는 테스트하기 쉽다. 특히 모의 객체(Mock Objects)를 사용하여 클래스의 동작을 테스트할 수 있다.
명시적인 상태 관리
: 클래스를 사용하면 상태 관리가 더 명시적이고 구조화된다. React Hooks도 상태 관리에 유용하지만, 복잡한 상태 로직을 관리하기에는 클래스 기반 접근이 더 효과적일 수 있다. 😤