React/youtube_clone_coding

검색 기능 Refactoring

느리지만 꾸준하게 2021. 8. 10. 18:25

MVC(model view control)라는 어플리케이션을 만들 때 쓸 수 있는 디자인 패턴이 있다.

어플리케이션을 모델과 뷰와 컨트롤러로 나누어서 만들 수 있는 것이다.

MVVM도 있고 안드로이드에서는 MVVM을 많이 쓰고 있다. MVI도 있고 MVP도 있고 다양한 디자인 패턴이 있다.

 

리액트는 뷰 레이어를 담당하고 뷰 레이어를 담당하는 것은 단순히 사용자에게 데이터를 보여주고 클릭이 되면 클릭 이벤트 자체를 처리해주는 뷰에 관련된 것만 말하는 것이고,

컴포넌트가 네트워크 통신도 할 수 있고, 비즈니스 로직도 처리할 수 있고, 모든 걸 다할 수 있는 걸로 뷰를 만들면 안된다.

 

본론으로 들어가서 지금까지 만든 app 컴포넌트의 문제점은 무엇일까?

컴포넌트에는 두 가지 문제를 꼽을 수가 있는데 코드안에 중요한 key가 들어가 있는 것이다. 나중에 깃허브에 올리면 보여지기 때문에 잘못된 것이다. 그리고 컴포넌트 안에서 네트워크 통신하는 로직이 들어있는거 이러한 것이 나쁜코드인 이유이다.

코드안에 key값이 있고 컴포넌트 안에서 네트워크 동신하는 로직이 들어있다.

나중에 컴포넌트를 unittest 하기 위해서 네트워크 통신하는 것도 검사를 해야한다. 네트워크가 끝날 때까지 unittest가 기다려야 한다. 그럼 unittest를 돌릴 때마다 네트워크 통신이 발생하게 된다. 최악의 구조이다.

 

그래서 컴포넌트를 멍청하게 만들어서 네트워크 통신 하는것을 자체적으로 따로 클래스를 만들어 놓고 클래스 자체에 필요한 것을 컴포넌트 안에다가 주입 즉, app에서 필요한 dependency를 주입해주면 DI(dependency injection)라고 불리는데 필요한 컴포넌트를 주입해주게 되면 나중에 유닛테스트 할 때는 실제로 네트워크 통신 하는 것이 아니라(클래스가 아니라), 네트워크 통신하는 척만 해주는 더미 mock 클래스를 전달해주면 유닛테스트가 한결 수월해진다.

네트워크 통신하는 것

결론은 컴포넌트 안에서 네트워크를 처리 하는 것은 좋지 않다. 그리고 키 같은 암호같은 것들은 절대 코드에 남겨두면 안된다.

그래서 components안에 새로운 폴더를 만들 것인데, service라는 폴더를 만들고 조금 비즈니스 네트워크 통신하는 로직을 service에 추가해 줄것이다. 여기에 youtube라는 클래스를 통해서 필요한 네트워크 처리를 할 것이다. 

 

여기 constructor에 key를 받아올 것인데, 받아온거 저장하고 나서 아까 app에서 만든 것과 비교하면서 반복적으로 쓰이는 것을 가지고 와서 this.getRequestOption을 기본적으로 설정해준다. 

class Youtube {
    constructor(key) {
      this.key = key;
      this.getRequestOptions = {
        method: 'GET',
        redirect: 'follow',
      };
    }

그리고 함수는 API 두 가지 정도가 나오는데 첫 번째는 mostPopular를 써주고 requestOptions는 this.getRequestOption로 바꿔주고 mostPopular를 호출하면 네트워크 통신해서 받아온 데이터를 items로 변환해서 리턴하는 API가 있다.

나머지는 검색하는 것인데, query를 받아서 해당하는 query에 맞게 검색하는 것이다. 동일하게 app.jsx에서 가져와서

search(query)에다가 붙여준다. 

mostPopular() {
  return fetch(`https://www.googleapis.com/youtube/v3/videos?part=snippet&chart=mostPopular&maxResults=25&key=${this.key}`,
       this.getRequestOptions
    )
      .then(response => response.json())
      .then.(result => result.items);
  }
  
search(query) {
  return fetch(`https://www.googleapis.com/youtube/v3/videos?part=snippet&chart=mostPopular&maxResults=25&key=${this.key}`,
        this.getRequestOptions
    )
        .then(response => response.json())
        .then(result =>
          result.items.map(item => ({ ...item, id: item.id.videiId})
        )
        .then(items => setVideos(items))
        .catch(error => console.log('error', error));
  }
}

필요한 dependency를 외부에서 받아오는데 youtube라는 props으로 받아 올건데 app을 쓰는 최종적인 인덱스에 가서

(index.js)에서 이제 youtube를 만들어야 한다.

new Youtube 한 다음에 key는 나중에 제공해줄 것이고, 유닛테스트를 할 때는 app 컴포넌트를 만들 때 네트워크 통신을 하지 않고 그냥 정해진 데이터를 리턴하는 클래스를 주입해준다. 

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './app';
import Youtube from './service/youtube';

const youtube = new Youtube(process.env.REACT_APP_YOUTUBE_API_KEY);
ReactDOM.render(
  <React.StrictMode>
    <App youtube={youtube} />
  </React.StrictMode>,
  document.getElementById('root')
);

그리고 app.jsx에서는 youtube 받은 걸로 검색할 때는 youtube에 있는 search라는 API를 이용하면 된다.

youtube.js파일에서 key값은 this.key로 해주고 백틱키로 주소를 묶어준다. 그리고 create react app사이트로 들어가서 참고하면서 진행한다.

 

.env파일을 최상위 폴더안에 만들고 REACT_APP_YOUTUBE_API_KEY=~(my key)라고 지정을 해주고 변수를 index.jss에 지정해준다.

gitignore에 들어가서 코드를 추가해준다.

# API KEYs
.env

yotube.js를 async로 변환되게 전구모양을 클릭해준다.

class Youtube {
    constructor(key) {
      this.key = key;
      this.getRequestOptions = {
        method: 'GET',
        redirect: 'follow',
      };
    }
  
    async mostPopular() {
      const response = await fetch(
        `https://www.googleapis.com/youtube/v3/videos?part=snippet&chart=mostPopular&maxResults=25&key=${this.key}`,
        this.getRequestOptions
      );
      const result = await response.json();
      return result.items;
    }
  
    async search(query) {
      const response = await fetch(
        `https://www.googleapis.com/youtube/v3/search?part=snippet&maxResults=25&q=${query}&type=video&key=${this.key}`,
        this.getRequestOptions
      );
      const result = await response.json();
      return result.items.map(item => ({ ...item, id: item.id.videoId }));
    }
  }
  
  export default Youtube;

최종 결과 app을 보면 검색는거와 다른거 두 가지 기능으로 굉장히 단순해졌다.

import React, { useEffect, useState } from 'react';
import styles from './app.module.css';
import VideoList from './components/video_list/video_list';
import SearchHeader from './components/search_header/search_header';

function App({ youtube }) {
  const [videos, setVideos] = useState([]);
  const search = query => {
    youtube
      .search(query) //
      .then(videos => setVideos(videos));
  };

  useEffect(() => {
    youtube
      .mostPopular() //
      .then(videos => setVideos(videos));
  }, []);
  return (
    <div className={styles.app}>
      <SearchHeader onSearch={search} />
      <VideoList videos={videos} />
    </div>
  );
}

export default App;

 

 

 

 

<출처 : DreamCoding 리액트 개념 정리+유튜브 클론코딩: ellie>

참고: https://academy.dream-coding.com/courses/react-basic

 

리액트 강의 (유튜브 클론 코딩 + 실시간 전송 명함 카드 만들기 웹앱 만들기)

리액트 전반적인 개념 설명과 (클래스 컴포넌트와 함수 컴포넌트 그리고 리액트 훅까지) 실전 유튜브 클론 코딩 프로젝트. Firebas의 실시간 데이터베이스를 이용해 멋진 명함 카드 만들기 웹 어

academy.dream-coding.com

참고: https://create-react-app.dev/docs/adding-custom-environment-variables

 

Adding Custom Environment Variables | Create React App

Note: this feature is available with react-scripts@0.2.3 and higher.

create-react-app.dev

 

'React > youtube_clone_coding' 카테고리의 다른 글

마무리 단계(Error잡기)  (0) 2021.08.16
video selection & detail screen  (0) 2021.08.11
검색 기능 구현하기  (0) 2021.08.10
Youtube 검색 기능 UI 구현  (0) 2021.08.10
list item 만들기  (0) 2021.08.09