![ddd](./assets/cocotubeMain.png)
드림코딩 리액트 - 유튜브 프로젝트 강의를 수강하며 만든 작업물을 디자인, 기능, 기술 스택을 추가하여 다시 작업한 개인프로젝트입니다.
- 주제 : 유튜브 클론 프로젝트
- 작업 기간 : 2023.08.07 ~ 2023.10.08
- 분류 : 개인 프로젝트
- firebase 로그인, 회원가입, 로그아웃, 회원탈퇴
- 구글, 깃허브 소셜 로그인
- 마이페이지 (좋아요 누른 영상 조회, 내 정보 수정)
- 영상 좋아요, 채널 구독
- 채널 페이지 조회
- 동영상 검색
- 음성 인식 검색
- 검색 필터링 기능
- 영상 댓글 조회
- 무한 스크롤
- 반응형웹
🚨 프로젝트에 사용된 Youtube Data Api v3 의 경우 하루 무료 사용량이 제한되어 있어 다양한 검색 요청이 들어가게 되면 사용량 초과로 인해 검색이 되지 않을 수 있습니다.
사용량 초과로 인해 배포 페이지 서비스가 제한적인 경우 불편하시더라도 데모 영상을 확인해주시면 감사하겠습니다.
COCOTUBE 배포 주소로 이동
![](https://camo.githubusercontent.com/02579431c7ba615acfd1b1151a74eadbb69f6ba82a2597baf3c49d2d6bddf541/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f46697265626173652d4646434132383f7374796c653d666f722d7468652d6261646765266c6f676f3d6669726562617365266c6f676f436f6c6f723d7768697465)
![](https://camo.githubusercontent.com/02579431c7ba615acfd1b1151a74eadbb69f6ba82a2597baf3c49d2d6bddf541/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f46697265626173652d4646434132383f7374796c653d666f722d7468652d6261646765266c6f676f3d6669726562617365266c6f676f436f6c6f723d7768697465)
![Video](https://camo.githubusercontent.com/c64eecb6e4d300204b96b08bf24bcef3a1a277ea9051c88bab0e2277ad076ce5/687474703a2f2f696d672e796f75747562652e636f6d2f76692f4259635f4450366e5838492f302e6a7067)
Home |
![Home](./assets/Home.gif) |
type FetchData = ({
pageParam,
}: {
pageParam?: string | undefined
}) => Promise<{ video: YoutubeVideoType; nextPageToken: boolean }>
const useInfiniteScroll = (
key: [string, string | undefined, SearchFilter | undefined],
fetchData: FetchData,
options = {},
) => {
const { ref, inView } = useInView({
threshold: 1,
})
const { isLoading, data, fetchNextPage, hasNextPage, error } =
useInfiniteQuery(key, fetchData, {
...options,
getNextPageParam: lastPage => lastPage.nextPageToken || undefined,
staleTime: STALE_TIME,
})
useEffect(() => {
if (inView && hasNextPage && !isLoading) {
fetchNextPage()
}
}, [inView, hasNextPage, fetchNextPage, isLoading])
return {
ref,
isLoading,
data,
fetchNextPage,
error,
}
}
export default useInfiniteScroll
//
const {
ref,
isLoading,
data: videoData,
error,
} = useInfiniteScroll(
['channelVideo', channelId, 'date'],
({ pageParam = undefined }) =>
youtubeClient.listChannelVideos(channelId, pageParam, 'date'),
)
- 비디오데이터 쿼리 캐싱과 무한 스크롤 구현이 필요한 컴포넌트가 다수 있어
React Query
의 useInfiniteQuery
와 react-intersection-observer
를 엮어 커스텀 훅으로 추상화 하였습니다. (Home, Search, Channel 페이지에서 사용됨)
회원가입 |
![signUp](./assets/signup.gif) |
로그인 |
![signIn](./assets/signIn.gif) |
Firebase Authentication
을 사용하여 회원가입, 로그인, 로그아웃, 구글/깃허브 소셜 로그인, 회원탈퇴 기능을 구현하였습니다.
AuthInput
공통 컴포넌트를 만들어 재사용하였습니다.
- 회원가입, 로그인 폼의 유효성 검사를 위해
React Hook Form
을 사용하였습니다.
마이페이지 |
![Mypage](./assets/MyPage.gif) |
- 닉네임 변경시
Firebase Authentication
에서 제공하는 updateProfile
메서드를 사용하여 DisplayName
을 변경하였습니다.
- 프로필 사진 변경시
Firebase Storage
에 이미지를 업로드 하고, Firebase Authentication
에서 제공하는 updateProfile
메서드를 사용하여 프로필 사진을 변경하였습니다.
Recoil
로 로그인한 유저의 정보를 전역 상태로 관리하여 프로필 사진 변경시 우측 상단의 프로필 사진이 즉시 변경되도록 구현하였습니다.
검색 |
![search](./assets/Search1.gif) |
react-speech-recognition
라이브러리를 사용하여 음성 인식 검색 기능을 구현하였습니다.(검색어 입력창에 마이크 아이콘을 클릭하면 음성 인식이 시작됩니다.)
const [filter, setFilter] = useState<SearchFilter>('relevance')
//
const {
ref,
isLoading,
data: videoData,
} = useInfiniteScroll(
['videos', searchKeyword, filter],
({ pageParam = undefined }) =>
youtubeClient.searchByKeyword(searchKeyword, pageParam, filter),
)
- 검색 필터링 기능을 구현하기 위해
useState
를 사용하여 filter
상태를 관리하였습니다.
filter
상태에 따라 api 함수의 파라미터를 변경하여 검색 결과를 필터링 하였습니다.
Search Keyword
와filter
에 따라 필터링된 결과를 캐싱하여 버튼을 누를 때마다 api 요청을 보내지 않도록 하였습니다.
비디오 디테일 |
![videoDetail](./assets/VideoDetail.gif) |
- 상위 컴포넌트에서
React Router
기능인 state
로 비디오 데이터를 전달받아 사용하였습니다.
- 상위 컴포넌트에서 받아온
video id
를 활용해 댓글 데이터를 불러왔습니다. api 사용 가능 횟수가 많지 않아 댓글 데이터는 최신순으로 25개만 불러오도록 구현했습니다.
React Query
를 활용하여 구독 버튼을 누르면 firestore
의 사용자 정보를 업데이트하고, 좌측의 구독 채널 목록이 즉시 업데이트 되도록 구현했습니다.
- 좋아요 기능 또한
React Query
를 활용하여 구현했습니다. 좋아요 누른 영상은 마이페이지에서 즉시 업데이트 되도록 했습니다.
채널 |
![channel](./assets/Channel.gif) |
- 해당 채널이 업로드한 비디오를 최신순으로 조회할 수 있습니다.
반응형 |
![responsive](./assets/responsive.gif) |
@mixin respond-to($size) {
@if $size == max-640 {
@media screen and (max-width: 640px) {
@content;
}
} @else if $size == max-768 {
@media screen and (max-width: 768px) {
@content;
}
} @else if $size == max-1024 {
@media screen and (max-width: 1024px) {
@content;
}
} @else if $size == max-1280 {
@media screen and (max-width: 1280px) {
@content;
}
} @else if $size == max-1400 {
@media screen and (max-width: 1400px) {
@content;
}
} @else if $size == min-769 {
@media screen and (min-width: 769px) {
@content;
}
}
}
// 사용 예시
.info {
@include alienCenter(20px);
@include respond-to(max-768) {
flex-direction: column;
text-align: center;
}
}
Sass
의 Mixin
을 활용하여 브레이크 포인트 별로 반응형 웹을 구현하였습니다.