ํ๋์ view๋ฅผ ๊ฐ์ ํ๊ณ ๋ง๋ youtube ๊ฒ์ ํ๋ก์ ํธ๋ฅผ ๋ง๋ค์ด ๋ณด๊ฒ ์ต๋๋ค.
$vue create youtube
$npm i --save axios lodash
์์ ๊ทธ๋ ค๋์ ๋ ์ด์์์ ๊ธฐ๋ฐ์ผ๋ก ๊ฐ๋ฐ์ ํด๋ณด๊ฒ ์ต๋๋ค.
SearchBar, VideoList ์ปดํฌ๋ํธ (VideoDetail์ปดํฌ๋ํธ๋ ์ฐจํ์)
๋ฐ์ดํฐ ํ๋ฆ :
SearchBar โ App
/
App
โโ VideoList โโ VideoListItem /
App
โ VideoDetail /
SearchBar โ App
<!--
components/SearchBar.vue
-->
<template>
<div>
<input class="searchBar" type="text" @keypress.enter="searchVideo">
</div>
</template>
<script>
import axios from 'axios'
const BASE_URL = 'https://www.googleapis.com/youtube/v3/search'
// #1
const API_KEY = process.env.VUE_APP_YOUTUBE_API_KEY
export default {
name: 'SearchBar',
data() {
return {
userInput:'',
}
},
methods:{
searchVideo(e) {
this.userInput = e.target.value
const config = {
params: {
part: 'snippet',
key: API_KEY,
q: this.userInput
}
}
axios.get(BASE_URL,config)
.then(res=>{
console.log('๋ฐ์ดํฐ ๋ณด๋ผ๊ฒ!')
this.$emit('searched-videos',res.data.items)
})
.catch(err=>{
console.log(err)
})
}
}
}
</script>
<style scoped>
.searchBar {
width: 100%;
padding: 0.5rem;
font-size: 1.25rem;
}
</style>
#1 : ์ด๊ฑธ ์ํด์ .env.loca
ํ์ผ์ ๋ง๋ค์ด์ ๋ทฐ์ ๋ณ์๋ฅผ ๊ด๋ฆฌํ์
โญ ๋ฐ์ดํฐ๋ฅผ ์์ ์ปดํฌ๋ํธ์๊ฒ ๋ณด๋ด๊ธฐ ์ํด์ this.$emit('searched-videos',res.data.items)
: ์์ ์ปดํฌ๋ํธ์์ searched-videos ๋ผ๋ ์ด๋ฒคํธ๊ฐ ์คํ๋๋ฉด, SearchBar.vue ์ปดํฌ๋ํธ์์ res.data.items๋ฅผ ์์์ปดํฌ๋ํธ์๊ฒ ๋ณด๋ด์ค๊ฑฐ์ผ!!โญ
<template>
<div id="app">
<header>
<h1>youtube</h1>
<!--#3-->
<SearchBar @searched-videos="searchedVideos"/>
</header>
<section>
</section>
</div>
</template>
<script>
// #1
import SearchBar from '@/components/SearchBar'
export default {
name: 'App',
components: {
// #2
SearchBar,
},
data() {
return {
// #4
videoList:[],
}
},
methods: {
// #5
searchedVideos(videoList) {
this.videoList = videoList
}
}
}
</script>
<style>
header,section {
width: 80%;
margin: 0 auto;
padding: 1rem 0;
}
section {
display:flex;
}
</style>
#1 : SearchBar ์ปดํฌ๋ํธ ๋ถ๋ฌ์ค๊ธฐ
#2 : SearchBar ์ปดํฌ๋ํธ ๋ฑ๋ก
#3 : SearchBar ์ปดํฌ๋ํธ ์ฌ์ฉํ๊ธฐ
selected-videos
์ด๋ฒคํธ๋ฅผ ์ ๊ณ , ์ด๋ฐ ์ด๋ฒคํธ๊ฐ ๋ฐ์์ searchedVideos
ํจ์ ์คํ! #4 : App.vue์์ ์ฌ์ฉํ ๋น๋์ค ๋ฆฌ์คํธ๋ฅผ ๋ด์ ๋ณ์
#5 : SearchBar์์ ๋์ด์จ ๋ฐ์ดํฐ๋ฅผ app.vue์ ์ ์ฅํ์!
App โ VideoList
<!--
components/VideoList.vue
-->
<template>
<div class="videoList">
<div
v-for="video in videoList"
:key="video.etag"
class="videoListItem"
>
<img width=360 :src="thumbnailUrl(video)" alt="">
<div>
<div class="videoListItem__title">{{video.snippet.title | unescape}}</div>
<div class="videoListItem__channelTitle">{{video.snippet.channelTitle}}</div>
<div class="videoListItem__description">{{video.snippet.description}}</div>
</div>
</div>
</div>
</template>
<script>
import _ from 'lodash'
export default {
name:'VideoList',
props: {
videoList: Array,
},
methods: {
thumbnailUrl(video) {
return video.snippet.thumbnails.high.url
},
},
filters: {
unescape(value) {
return _.unescape(value)
}
},
}
</script>
<style>
.videoList {
width: 30%;
}
.videoListItem {
display:flex;
}
.videoListItem__title {
font-size:1.25rem;
font-weight:bold;
}
</style>
โญApp์์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ค๊ธฐ ์ํด์
props: {videoList: Array,}
๋ฅผ ํตํด ์ด๋ค ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ค๋ ์ง ์ ์ํด์ค์ผ ํฉ๋๋ค.โญ
<template>
<div id="app">
<header>
<h1>youtube</h1>
<SearchBar @searched-videos="searchedVideos"/>
</header>
<section>
<!-- #3 -->
<VideoList :videoList="videoList"/>
</section>
</div>
</template>
<script>
import SearchBar from '@/components/SearchBar'
// #1
import VideoList from '@/components/VideoList'
export default {
name: 'App',
components: {
SearchBar,
// #2
VideoList,
},
data() {
return {
videoList:[],
}
},
methods: {
searchedVideos(videoList) {
this.videoList = videoList
}
}
}
</script>
<style>
header,section {
width: 80%;
margin: 0 auto;
padding: 1rem 0;
}
section {
display:flex;
}
</style>
#1 : VideoList ์ปดํฌ๋ํธ ๋ถ๋ฌ์ค๊ธฐ
#2 : VideoList ์ปดํฌ๋ํธ ๋ฑ๋ก
#3 : VideoList ์ปดํฌ๋ํธ ์ฌ์ฉํ๊ธฐ
:videoList
๋ VideoList ์ปดํฌ๋ํธ์์ videoList
๋ผ๋ ๋ณ์๋ช
์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ ๊ฒ์ด๋ผ๋ ์๋ฏธ ( VideoList์ปดํฌ๋ํธ์ ๊ทธ๋ ๊ฒ ์ ์๋ฅผ ํด๋จ์!)="videoList
๋ App.vue์์ ์ฐ๊ณ ์๋ videList
๋ฐ์ดํฐ๋ฅผ ๋ณด๋ด์ค ๊ฒ์ด๋ผ๋ ์๋ฏธ์์ง VideoList โ VideoListItem ์ผ๋ก ๋์ด๊ฐ๋ ๊ฒ์ ๊ตฌํํ์ง ์์์ต๋๋ค. ์ด๊ฑธ ํด๋ณผ๊ฒ์
VideoListItem โโ VideoList โโ App
<template>
<!-- #4-1 -->
<div @click="selectVideo" class="videoListItem">
<!-- #3-1 -->
<img :src="thumbnailUrl(video)" alt="">
<div>
<!-- #5-1 -->
<div class="videoListItem__title">{{video.snippet.title | unescape}}</div>
<div class="videoListItem__channelTitle">{{video.snippet.channelTitle}}</div>
<div class="videoListItem__description">{{video.snippet.description}}</div>
</div>
</div>
</template>
<script>
import _ from 'lodash'
export default {
// #1
name: 'VideoListItem',
// #2
props: {
video:Object,
},
methods: {
// #3
thumbnailUrl(video) {
return video.snippet.thumbnails.high.url
},
// #4
selectVideo(){
this.$emit('select-video',this.video)
}
},
// #5
filters: {
unescape(v){
return _.unescape(v)
}
}
}
</script>
<style scoped>
.videoListItem {
/* display:flex; */
}
.videoListItem__title {
font-size:1.25rem;
font-weight:bold;
}
.videoListItem > img {
width:100%;
}
</style>
#1 : ์ปดํฌ๋ํธ ์ด๋ฆ ์ ์
#2 : VideoList์์ ๋ฐ์์ค๋ ๋ฐ์ดํฐ props ์ ์
#3 : ์ธ๋ค์ผurl ๋ฐ์์ค๋ ํจ์ ์ ์ ๋ฐ ์ฌ์ฉ
#4 : ๋น๋์ค ์ ํ์ ๊ทธ ๋น๋์ค๋ฅผ ์์ ์ปดํฌ๋ํธ(๋ถ๋ชจ)๋ก ๋ณด๋ด์ฃผ๊ธฐ! select-video
๋ผ๋ ์ด๋ฒคํธ๋ฅผ ๋ง๋ค์๋ค!
#5 : ํํฐ๋ฅผ ์ ์ํ ์๋ ์๋ค์!! ๋ณด๊ธฐ๋ถํธํ๋จ์ด๋ ์์ ์ฃผ๋ ๊ธฐ๋ฅ! โญ
<template>
<div class="videoList">
<!-- #3 -->
<VideoListItem
v-for="video in videoList"
:key="video.etag"
:video="video"
@select-video="selectVideo"
/>
</div>
</template>
<script>
// #1
import VideoListItem from '@/components/VideoListItem'
export default {
name:'VideoList',
components: {
// #2
VideoListItem
},
props: {
videoList: Array,
},
methods: {
// #4
selectVideo(video) {
this.$emit('select-video',video)
}
},
}
</script>
<style>
.videoList {
width: 30%;
}
</style>
#1 : VideoListItem ์ปดํฌ๋ํธ ๋ถ๋ฌ์ค๊ธฐ
#2 : VideoListItem ์ปดํฌ๋ํธ ๋ฑ๋ก
#3 : VideoListItem ์ปดํฌ๋ํธ ์ฌ์ฉํ๊ธฐ
#4 : VideoListItem์์ select-video
์ด๋ฒคํธ ๋ฐ์์ ์์ ์ปดํฌ๋ํธ(๋ถ๋ชจ)์๊ฒ video ๋ณด๋ด์ฃผ๊ธฐ
<template>
<div id="app">
<header>
<h1>youtube</h1>
<SearchBar @searched-videos="searchedVideos"/>
</header>
<section>
<!-- #1 -->
<VideoList :videoList="videoList" @select-video="selectVideo"/>
</section>
</div>
</template>
<script>
import SearchBar from '@/components/SearchBar'
import VideoList from '@/components/VideoList'
export default {
name: 'App',
components: {
SearchBar,
VideoList,
},
data() {
return {
videoList:[],
// #1-1
selectedVideo: {},
isVideoSelected:false,
}
},
methods: {
searchedVideos(videoList) {
this.videoList = videoList
},
// #1-2
selectVideo(video){
console.log('select๋น๋์ค ํธ์ถ!')
console.log(video)
this.selectedVideo = video
this.isVideoSelected = true
},
}
}
</script>
<style>
header,section {
width: 80%;
margin: 0 auto;
padding: 1rem 0;
}
section {
display:flex;
}
</style>
#1 : VideoList์ปดํฌ๋ํธ์์ select-video
์ด๋ฒคํธ ๋ฐ์์ selectVideo
ํจ์ ์คํ๋๋ค.
selectedVideo
๋ ์ ํ๋ ๋น๋์ค๊ฐ์ฒด ์ ์ฅํ ๋ณ์ / isVideoSelected
๋ ์ ํ๋ ๋น๋์ค๊ฐ ์๋ ์ง ์ฌ๋ถ
App โ VideoDetail
<template>
<div class="videoDetail">
<div class="videoContainer">
<!-- #2 -->
<iframe
:src="videoUrl"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
>
</iframe>
</div>
</div>
</template>
<script>
export default {
name: 'VideoDetail',
// #1
props: {
video:Object,
},
// #2
computed: {
videoUrl() {
const videoId = this.video.id.videoId
const url = `https://www.youtube.com/embed/${videoId}`
return url
}
}
}
</script>
<style>
.videoDetail {
width: 70%;
padding-right: 1rem;
}
.videoContainer {
position: relative;
padding-top: 56.25%;
}
.videoContainer > iframe {
position: absolute;
top:0;
width:100%;
height: 100%;
}
</style>
#1 : ์์ ์ปดํฌ๋ํธ์์ ๋ฐ์์ฌ ๋ฐ์ดํฐ props:{video:Object}
#2 : ๋๋ videourl๋ง ํ์ํ๋๊น ๊ทธ๊ฑธ ๊ณ์ฐํด์ ์ 2์ ๋ฐ์ดํฐ๋ก ์ฐ์! computed:{videoUrl(){...}}
<template>
<div id="app">
<header>
<h1>youtube</h1>
<SearchBar @searched-videos="searchedVideos"/>
</header>
<section>
<!-- #3 -->
<VideoDetail v-if="isVideoSelected" :video="selectedVideo"/>
<VideoList :videoList="videoList" @select-video="selectVideo"/>
</section>
</div>
</template>
<script>
import SearchBar from '@/components/SearchBar'
import VideoList from '@/components/VideoList'
// #1
import VideoDetail from '@/components/VideoDetail'
export default {
name: 'App',
components: {
SearchBar,
VideoList,
// #2
VideoDetail,
},
data() {
return {
videoList:[],
selectedVideo: {},
isVideoSelected:false,
}
},
methods: {
searchedVideos(videoList) {
this.videoList = videoList
},
selectVideo(video){
console.log('select๋น๋์ค ํธ์ถ!')
console.log(video)
this.selectedVideo = video
this.isVideoSelected = true
},
}
}
</script>
<style>
header,section {
width: 80%;
margin: 0 auto;
padding: 1rem 0;
}
section {
display:flex;
}
</style>
#1 : VideoDetail์ปดํฌ๋ํธ ๋ถ๋ฌ์ค๊ธฐ
#2 : VideoDetail ์ปดํฌ๋ํธ ๋ฑ๋กํ๊ธฐ
#3 : VideoDetail ์ปดํฌ๋ํธ ์ฌ์ฉํ๊ธฐ ์ด๋ ์ด ์ปดํฌ๋ํธ๋ isVideoSelected
๊ฐ ์ฐธ์ผ ๋๋ง ์ฌ์ฉํจ. ๊ทธ๋ฆฌ๊ณ VideoDetail
์ปดํฌ๋ํธ๋ก selectedVideo
๋ฅผ ๋ณด๋ด์ค๊ฑฐ์ผ!
๋น๋์ค ์ ํ์
vue devtools (google extension)