Youtube 구현해보기 - 1

19506 단어 ReactReact

React 도전기

Youtube API를 받아서 나만의 Youtube를 만들어보도록해보자.

인기동영상 api 가져오기

나만의 youtube main화면에 보여질 동영상의 리스트가 필요했다. 친절하게도 우리의 youtube님 께서는 무료로 제공하셔서 손쉽게 가져올수있었다.

fetch로 먼저 불러와보자

나중에 유지보수도 쉽게하기위해서 나는 우선 youtube.js 라는 파일을 만들어 그곳에 모든 api를 불러 받아올 예정이다.

API key값은 보여지면 안되므로 따로 만들어 불러왔다.

class Youtube{
	constructor(key){
    	this.key = key;
        this.getRequestOptions = {
        	method : "GET",
            redirect : "follow",
     };
    }
    
    async mostPopular(){
    	const response = await fetch(
      `https://youtube.googleapis.com/youtube/v3/videos?part=snippet&part=contentDetails&part=player&chart=mostPopular&maxResults=25&key=${this.key}`,
      this.getRequestOptions
    )
    const result = await response.json();
    return result.items;
  }
}

export default Youtube;

그래서 index.js에서는

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")
);

youtube라는 클래스를 import하여 state값을 App에게 보내주고,

App.jsx에서 youtube를 props로 받아 컴포넌트가 렌더링될때마다 특정 작업을 실행할 수 있도록 useEffect를 사용하여 youtube.mostPopular()함수를 실행시키고 .then 실행이되었다면, videos안에 결과값을 받아온다.

import React, { useState, useEffect } from "react";

const App = ({ youtube }) => {
  const [videos, setVideos] = useState([]);

  useEffect(() => {
    youtube.mostPopular().then((videos) => setVideos(videos));
  }, []);

  console.log(videos);

  return (
    <>
      <div>리액트 드럽게 어렵네.</div>
    </>
  );
};

export default App;

이처럼 동영상 리스트를 받아오는걸을 확인할수있다.

개인적으로 axios 라이브러리를 이용하는게 fetch보다 편하다는 생각이들어 axios를 한번 적용해보자

axios의 장점으로는

IE까지 대부분의 브라우저를 지원한다. (구형 포함)
JSON 데이터를 자동 변환해준다. (.json() 사용할 필요 없음)
...등등

axios를 쓰면 baseURl, Params를 손쉽게 정의할수도있다.

const httpClient = axios.create({
	baseURL : "https://youtube.googleapis.com/youtube/v3",
    params : { key: process.env.REACT_APP_YOUTUBE_API_KEY },
    });
    
// 받은 httpClient를 yotubue의 인자로 보내준다.
const youtube = new Youtube(httpClient)

ReactDOM.render(
  <React.StrictMode>
    <App youtube={youtube} />
  </React.StrictMode>,
  document.getElementById("root")
);
class Youtube{
	constructor(httpClient){
    	this.youtube = httpClient
    }
    
    async mostPoular(){
    	const response = await this.youtube.get('videos', {
        	params : {
            	part : "snippet",
                chart : "mostPopular",
                maxResult : 25,
        }
    }
    return response.data.items;
}

export default Youtube

이런식으로 기존의 youtube클래스에서 data를 불러오는게 훨씬 직관적이고 편하게 불러올수있게되었다. 그러면 나머지 필요한 API들도 불러와보자. 나는 search, channel 두개가 더필요했다.

그전에 mostPopular에서 나는 Part에서 snippet말고 두개 더 필요한 정보가있어서 불러왔다.

 part: "snippet, contentDetails",

검색 api 가져오기

그리고, search 기능하기위해서 search API를 불러와보자.

아까의 youtube.js에

// 검색어(query)를 받아서 기존의 item data에 새로운 videoid값을 부여

async search(query){
	const response = await.this.youtube.get('search, {
    	params:{
        	part : "snippet",
            maxResults : 25,
            type : "video",
            q : query,
            },
        });
        return reponse.data.items.map((item) => ({
        	...item,
            id: item.id.videoId,
        }));
}
        

그리고 app.jsx에서는

  import React, { useState, useEffect } from "react";
import Nav from "./components/search/nav";

const App = ({ youtube }) => {
  const [videos, setVideos] = useState([]);

// Nav에서 검색어(query)를 받아서 다시 그걸 youtube 인자로 전달
  const search = (query) => {
    youtube
      .search(query) //
      .then((videos) => {
        setVideos(videos);
      });
  };

  useEffect(() => {
    youtube
      .mostPopular() //
      .then((videos) => setVideos(videos));
  }, [youtube]);

  console.log(videos);

  return (
    <>
      <Nav onSearch={search}></Nav>
      <div>진튜브</div>
    </>
  );
};

export default App;

Nav 컴포넌트를 이제 만들어보자.
onClick, onKeyPress함수를 이용하여 input.value를 받아 그것을 다시
App에 onSearch 를 호출하면서 넘겨주었다.

const Nav = ({ onSearch }) => {
  const inputRef = useRef();
  const handleSearch = () => {
    const value = inputRef.current.value;
    onSearch(value);
  };

  const onClick = () => {
    handleSearch();
  };

  const onKeyPress = (event) => {
    if (event.key === "Enter") {
      handleSearch();
    }
  };

  return (
    <ul className={styles.nav}>
      <li className={styles.nav_left}>
        <ul className={styles.menu_part}>
          <li>
            <i className={`fas fa-bars ${styles.bar}`}></i>
          </li>
          <li>
            <img src={Logo} alt="logo" className={styles.logo} />
          </li>
          <li>
            <form action="">
              <input
                type="text"
                className={styles.search_bar}
                maxLength="30"
                ref={inputRef}
                onKeyPress={onKeyPress}
              ></input>
              <button className={styles.search_btn} onClick=						{onClick}>
                <i className="fas fa-search"></i>
              </button>
            </form>
          </li>
        </ul>
      </li>
      <li className={styles.nav_right}>
        <ul className={styles.profile_icons}>
          <li>
            <i className="far fa-caret-square-up"></i>
          </li>
          <li>
            <i className="fas fa-th-large"></i>
          </li>
          <li>
            <i className="far fa-bell"></i>
          </li>
          <li>
            <i className="far fa-user-circle"></i>
          </li>
        </ul>
      </li>
    </ul>
  );
};

export default Nav;

흠.... 아무런 오류가 없는거같은데. button과 keypress를 할때마다 새로고침되어 결과값을 볼수없게되었다. 아... 알고보니 form을 넣었더니 새로고침이 되었던것이다. 그래서 form을 편하게 지워주자.

useCallback, memo

그리고 onClick 과 keyPress라는 함수를 선언했는데, 이렇게 선언을 하게되면 컴포넌트가 리렌더링 될때마다 함수들이 새로성성되게된다. 문제는 되지는않지만, 렌더링이 자주 발생되거나 컴포넌트가 많을시에 최적화를 해주는데 그것을 해주기위해서 memo, useCallback을 사용할것이다. 방법은 너무 간단하다.

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

useCallback 의 첫번째 파라미터에는 생성해주고 싶은 함수를 넣어주고, 두번째 파라미터에는 의존성 값의 배열을 넣어주면 된다. useCallback은 콜백의 메모제이션된 버전을 반환하는데 이말은 메모이제이션된 버전은 콜백의 의존성이 변경되었을 때에만 변경된다는것이다. 어렵다... 그냥 쉽게 말하면 이전에 생성한 함수를 저장해두고 재사용한다는것이다. 그리고 함수 내부에서 의존하는 상태값이 있다면, 반드시 두번째 인자 배열에 명시해야한다.

그리고 memo를 이용해보자..

우선 여기서 왜 Memo를 써야하는지 알아보자면, React는 먼저 컴포넌트를 렌더링 한뒤, 이전 렌더링 결과와 비교하여 DOM업데이트를 결정하는데 만약 이때, 렌더결과가 이전과 다르다면 DOM을 업데이트하다. 이러한 원리에서 작동하는 React의 속도는 굉장히 빠르지만 여기서 더욱 속도를 높일수있는데, memo를 쓰는것이다. 컴포넌트가 memo로 래핑이되면 컴포너는트를 렌더링하여 결과를 memorizing하게되어진다. 그리고 다음 렌더링이 일어났을때, props가 가다면, react는 기억된 내용을 재사용한다.

그래서 하는방법은 아래와 같다. 그냥 memo로 감싸주기만 하면된다.


import React, { useRef, memo } from "react";
import styles from "./nav.module.css";
// import { Link } from "react-router-dom";

const Nav = memo(({ onSearch }) => {

...code}

)

part2 에선 list를한번만들어보자

좋은 웹페이지 즐겨찾기