본문 바로가기
학원수업/4월

04/06 국비학원 개발자과정 90회차- Redux, YoutubeAPI

by 코딩마스터^^ 2023. 4. 6.

Redux는 props넘길때 자동으로 훨씬 편하게 만들어 준다.

 

Redux로 로그인 로그아웃 하기

uid를 로컬 스토리지에 담아주기

로그인 후 로그아웃도 구현한다.

그런데 로그인이나 로그아웃하고  페이지 새로고침 하고싶다.

원래는 useNavigate 훅을 사용하는게 좋다.

그런데 여기서는 안된다.

그래서 window.location.reload()로 처리했다.

 

컴포넌트 쪼개기 연습-youtubeAPI

 

https://developers.google.com/youtube/v3/getting-started?hl=ko 

 

시작하기  |  YouTube Data API  |  Google Developers

시작하기 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 소개 이 문서는 YouTube와 상호작용할 수 있는 애플리케이션을 개발하려는 개발자를 위해 작성되었

developers.google.com

 

Youtube 인기 동영상 25개 가져오기

import axios from "axios";
import { useEffect, useState } from "react";
import "./App.css";
import VideoList from "./components/VideoList";

const App =()=> {
  console.log('App');
  const [videos,setVideos]=useState([])//초기화에 []배열기호 넣어야한다. 배열들이 들어옴
  const [params, setParams]=useState({
  part:'snippet',
  chart:'mostPopular',
  maxResults:25,
  regionCode:'US',
  key:'비밀^^'
})

  useEffect(()=>{
    console.log('Effect');
    axios.get('https://youtube.googleapis.com/youtube/v3/videos?',{params})
    .then(result=>{//콜백...then
      console.log(result.data.items);//배열
      setVideos(result.data.items)//배열을 담아줌
    }).catch(error=>console.log(error))//catch콜백함수로 오류 잡기  
  },[])//의존성 배열 안넣으면 무한루프 돈다!! useSate의 인자를 의존성 배열에 넣는다=>리렌더링이 일어나는 조건
  console.log(videos);
  return (
    <>{/* 루트니까 fragment권장. 중첩 안된다. */}
      <VideoList videos={videos}/>
    </>
  );
}

export default App;

 

import React from 'react'
import styled from 'styled-components';
import VideoItem from './VideoItem';

const UL=styled.ul`
  display:flex;
  flex-wrap:wrap;
  list-style:none;
  padding-left:0;
  magin:0;
`

const VideoList = ({videos}) => {//videos를 받아와서 구조분해할당?
  console.log(videos);
  return (
    <UL>
      {//리액트문법
      videos.map(video=>(//맵에서는 ()소괄호
        <VideoItem key={video.id} video={video}/>
        
      ))
    }
    </UL>
  )
}

export default VideoList

 

import React from 'react'
import styled from 'styled-components'

const LI=styled.li`
  width:50%;
  padding:0.2em;
`
const VIVDEODIV=styled.div`
  width:100%;
  height:100%;
  display:flex;/* 비디오가 한줄에 나오게 함 */
  align-items:center;
  border:1px solid lightgray;
  box-shadow:3px 3px 5px 0px rgba(191,191,191,0.55);/* 그림자 효과 */
  cursor:pointer;
  transition: transform 250ms easy-in;/*자연스럽게 처리  */
  &:hover{
    transform:scale(1.02);
  }
`
const IMG=styled.img`
  width:40%;
  height:100%;
`
const DIV=styled.div`
  margin-left:0.3em;
`
const PTITLE=styled.p`
  margin:10;
  font-size:0.8rem;
`
const PCHANNEL=styled.p`
  margin:0;
  font-size
`

const VideoItem = (props) => {//vidoe를 구조분해할당함
  return (
    <LI>
      <VIVDEODIV>
      <div>
        <IMG src={props.video.snippet.thumbnails.medium.url} alt="video thumbnail"/>
        <DIV>
        <PTITLE>{props.video.snippet.title}</PTITLE>
        <PCHANNEL>{props.video.snippet.channelTitle}</PCHANNEL>
        </DIV>
      </div>
      </VIVDEODIV>
    </LI>
  )
}

export default VideoItem

유투브 페이지 분할하기

 

하나의 페이지로 구현을 해보자!

검색기능 넣기

 {
        selectedVideo&&(
          <div>
            <VideoDetail video={selectedVideo}/>
          </div>
        )
      }

이런 형식 너무 생소했다.

태그자리에 selectedVideo가 true이면 VideoDetail로 props를 보낸다는 뜻.

 

이 코드는 JSX로 작성되었으며 일반적으로 React와 함께 사용됩니다.

이 코드는 selectedVideo 변수의 진실성을 평가하여 선택된 비디오가 있는지 여부를 확인합니다. selectedVideo가 진실 값이면 (즉, null, undefined, false, 0 또는 빈 문자열이 아님), 선택한 비디오가 props로 전달되며 VideoDetail 컴포넌트가 렌더링됩니다.

selectedVideo가 거짓 값이면 아무것도 렌더링되지 않습니다.

전반적으로 이 코드는 선택한 비디오의 존재 여부에 따라 VideoDetail 컴포넌트를 조건부로 렌더링합니다.

import axios from 'axios'
import React, { useEffect, useState } from 'react'
import VideoList from '../VideoList'
import Form from 'react-bootstrap/Form';
import InputGroup from 'react-bootstrap/InputGroup';
import { Button } from 'react-bootstrap';
import VideoDetail from '../VideoDetail';
//기능 추가-사용자가 입력한 키워드를 관리-바뀔때마다 조회결과 달라져야
//1.검색기 추가
//2.비디오 선택시 상세페이지 처리
//
const YoutubePage = () => {
  //사용자가 입력한 키워드 관리를 위해서 훅을 선언해야한다.
  const [keyword, setKeyword]=useState("")

  //상세화면 추가로 인한 훅 필요
  const [selectedVideo, setSelectedVideo]=useState(null)

  //비디오가 선택되면 상태값을 관리해야한다.
  const videoSelect=(video)=>{//이벤트. List로넘기고 item으로 두단계 넘겨야...
    setSelectedVideo(video)
  } 

  const [videos,setVideos]=useState([])//초기화에 []배열기호 넣어야한다. 배열들이 들어옴
  const [params, setParams]=useState({
  part:'snippet',
  chart:'mostPopular',
  maxResults:25,
  regionCode:'US',
  key:'유투브API키'
})

const [params2, setParams2]=useState({//검색시의 데이터 가져오기 근데 이거 안쓴
  part:'snippet',
  type:'video',
  maxResults:25,
  // regionCode:'US',
  q:`${"여행"}`,
  key:'유투브API키'
})

  
useEffect(()=>{
  console.log('Effect');
  axios.get('https://youtube.googleapis.com/youtube/v3/videos?',{params})
  .then(result=>{//콜백...then
    console.log(result.data.items);//배열
    setVideos(result.data.items)//배열을 담아줌
  }).catch(error=>console.log(error))//catch콜백함수로 오류 잡기  
},[params])//의존성 배열 안넣으면 무한루프 돈다!! useSate의 인자를 의존성 배열에 넣는다=>리렌더링이 일어나는 조건


  const youtubeSearch=()=>{//함수 이름을 대문자로 하면 컴포넌트 취급을 받는다=화면이다/ 바벨번들러가... 주의하기!!
    axios.get(`https://youtube.googleapis.com/youtube/v3/search?part=snippet&maxResults=25&q=${keyword}&type=video&key=AIzaSyCTPwHhUxcmNTC6iLNV1MdKqAaz535ltls`)
    .then(result=>{//콜백...then
      console.log(result.data.items);//배열
      setVideos(result.data.items)//배열을 담아줌
    }).catch(error=>console.log(error))//catch콜백함수로 오류 잡기  
  }

    const changeKeyword=(event)=>{
      console.log(event.target.value);
      setKeyword(event.target.value)
    }

  return (
    <div className='container'>
      <div>
        <h2>Youtube<small>도튜브</small></h2>
        <hr/>
      </div>
      <InputGroup className="mb-3">
        <Form.Control
          placeholder="검색어"
          aria-label="검색어"
          aria-describedby="basic-addon2"
          onChange={changeKeyword}
        />
        <Button className='btn btn-danger' onClick={youtubeSearch}>Search</Button>
      </InputGroup>
      {
        selectedVideo&&(
          <div>
            <VideoDetail video={selectedVideo}/>
          </div>
        )
      }
      <VideoList videos={videos} videoSelect={videoSelect}/>
    </div>
  )
}

export default YoutubePage
import React from 'react'
import styled from 'styled-components';
import VideoItem from './VideoItem';

const VideoListDiv=styled.div`
  display:grid;
  magin-top:10px;
  grid-template-columns:repeat(auto-fill, minmax(320px, 1fr));
  
`

const VideoList = ({videos,videoSelect}) => {//videos를 받아와서 구조분해할당?
  console.log(videos);
  return (
    <>
    <VideoListDiv>

      {//리액트문법
      videos.map(video=>(//맵에서는 ()소괄호
      <VideoItem key={video.id} video={video} videoSelect={videoSelect}/>
      
      ))
    }
    </VideoListDiv>
    </>
  )
}

export default VideoList
import React from 'react'
import styled from 'styled-components'

const VideoLi=styled.li`
  width:50%;
  padding:0.2em;
  list-style:none;
`
const VideoCard=styled.div`
  width:100%;
  height:100%;
  display:flex;/* 비디오가 한줄에 나오게 함 */
  align-items:center;
  border:1px solid lightgray;
  box-shadow:3px 3px 5px 0px rgba(191,191,191,0.55);/* 그림자 효과 */
  cursor:pointer;
  transition: transform 250ms easy-in;/*자연스럽게 처리  */
  &:hover{
    transform:scale(1.02);
  }
`
const VideoThumbnail=styled.img`
  width:40%;
  height:100%;
`
const VideoInfo=styled.div`
  margin-left:0.3em;
`
const Ptitle=styled.p`
  margin:10;
  font-size:0.8rem;
`
const Pchannel=styled.p`
  margin:0;
  font-size
`

const VideoItem = (props) => {//vidoe를 구조분해할당함
  const {video, videoSelect}=props
  //첫번째 파라미터는 비디오 한건에 대한 정보
  //두번째 파라미터는 선택된 비디오의 이벤트 처리 함수의 주소번지를 받아서
  //VideoLi가 클릭 되었을때 파라미터로 video한건의 주소번지를 담아서 
  //부모에서 정의된 이벤트 처리 함수를 호출한다.
  //VideoList에서 이벤트 처리를 마무리 하지 않고 props로 넘기는 이유가 뭔가요?
  //VideoList에서는 n건을 가지고 있고 이 중에서 어떤 비디오 클립이 선택 되었는지 알 수 없으니까...
  //이벤트 소스 클립은 리스트에 있지만 선택된 비디오 한건에 대한 정보는 VideoItem에서 결정 된다.
  //그러니까 비디오 한건에 대한 정확한 정보를 알고 있는 자손 컴포넌트인 VideoItem에서 
  //부모가 가진 함수의 주소번지를 props로 받고 이벤트 호출은 VideoItem에서 처리 해야 함
  return (
    <VideoLi onClick={()=>videoSelect(video)}>
      <VideoCard>
      <div>
        <VideoThumbnail src={props.video.snippet.thumbnails.medium.url} alt="video thumbnail"/>
        <VideoInfo>
        <Ptitle>{props.video.snippet.title}</Ptitle>
        <Pchannel>{props.video.snippet.channelTitle}</Pchannel>
        </VideoInfo>
      </div>
      </VideoCard>
    </VideoLi>
  )
}

export default VideoItem
import React from 'react'

const VideoDetail = ({video}) => {
  console.log(video);
  return (
    <>
      <section>
        <iframe 
        title="video play" type="text/html" width="100%" height="500px" 
        src={`http://www.youtube.com/embed/${video.id.videoId}`} 
        frameborder="0" allowFullScreen></iframe>
      </section>
    </>
  )
}

export default VideoDetail

댓글