-
Notifications
You must be signed in to change notification settings - Fork 13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[6주차] Team 케이크WAY 김류원 & 지민재 미션 제출합니다 #13
base: main
Are you sure you want to change the base?
Conversation
BottomNavBar 구현 및 MovieList 불러오기 구현
Mimizae - 자그마한 수정들...
Coment: 주석 변경
Feat: 검색 페이지 무한 스크롤 구현 & 스켈레톤 화면 웨이브로 수정
mimizae... 마지막이길...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
과제하시느라 정말 고생많으셨습니다! 코드에서 fetch나 효율관련해서 고민하신 흔적이 많이 보였습니다. 특히 링크 들어갔을 때 보이는 애니메이션들이 잘 적용되고, 적재적소에 쓰인 것이 인상깊었습니다! 애니메이션을 잘 활용하지 못하는 제게 많은 배움이 있었습니다 😬 추가적으로 Next.js의 장점 중 하나인 서버 컴포넌트를 활용하는 데 있어 styled-component는 클라이언트 컴포넌트에서만 쓰일 수 있기에 tailwindcss or module.css 등의 css 스타일도 고려해보시면 좋을 것 같습니다! CSS-in JS 코드 정말 최고인 것 같습니다. 고생많으셨습니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
svg를 컴포넌트화 해서 반복사용, 동적 속성 등의 장점을 챙겨가는 부분이 좋은 것 같습니다! 다만 public 폴더 내 파일은 정적 파일로 취급되어 코드 스플리팅 등의 최적화 이점이 없어져서 src/components/icons 이렇게 분류해서 빌드 시의 장점도 챙겨가면 좋을 것 같아요!
export default async function MovieDetail(props: PageProps) { | ||
const { movieId } = await props.params; | ||
|
||
const movie = await getMovieInfoByMovieId(movieId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
api key를 감추기 위해 서버 컴포넌트에서 데이터 호출한 점! 좋은 것 같습니다
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
파일명이 달라도 같은 폴더 내의 두 개의 layout이 있는 건 추후 혼동을 줄 수 있을 것 같아요!
export const getPopularMovies = () => fetchMovies("movie/popular"); | ||
export const getPreviewMovies = () => fetchMovies("movie/top_rated"); | ||
export const getUpcomingMovies = () => fetchMovies("movie/upcoming"); | ||
export const getTrendingMovies = () => fetchMovies("trending/movie/week"); | ||
export const getNGTrendingMovies = () => fetchMovies("trending/movie/day"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
category를 동적으로 해서 하나의 함수로 하는 방법도 좋을 것 같습니당
const shuffleArray = (array: Movie[]) => { | ||
// 배열을 무작위로 섞는 함수 | ||
for (let i = array.length - 1; i > 0; i--) { | ||
const j = Math.floor(Math.random() * (i + 1)); | ||
[array[i], array[j]] = [array[j], array[i]]; // 배열 요소를 교환 | ||
} | ||
return array; | ||
}; | ||
|
||
// getRandomMovies 함수에서 인기 영화를 가져와서 랜덤으로 렌더링 | ||
export const getRandomMovies = async () => { | ||
const popularMovies = await getPopularMovies(); // 인기 영화 20개-> TMDB에서 20개로 제한 | ||
// 성인 영화 제외 | ||
const filteredMovies = popularMovies.filter((movie: Movie) => !movie.adult); | ||
|
||
const shuffledMovies = shuffleArray(filteredMovies); // 영화 순서 랜덤으로 섞기 | ||
return shuffledMovies; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
util 함수들은 따로 분리하여 사용하면 movieApi.ts 파일의 목적이 더 뚜렷하게 보일 것 같아요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
api ROUTE라는 NextJS만의 로직은 저도 이번에 알게 되었지만 서버의 흉내를 낸다는 게 참 매력적인 것 같아요 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
스켈레톤 코드... 👍 멋집니다
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fallback으로 로딩 시 ui를 보여주는 것을 바로 적용한 점 최고십니다! 혹시 Suspense를 사용하기 위해 클라이언트 컴포넌트로 전환하셨는지 궁금합니다
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넘겨주는 방법으로 상태관리를 써서 전역으로 상태를 관리하는 방법도 있을 것 같습니다 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tv와 관련된 api는 따로 구현 안 하신건지 궁금합니당
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이번 과제하느라구 수고 많았어요💗💗
민재의 장점은 배운 것을 바로바로 적용하는 모습같아요..👍
저는 제가 아는 것을 최대로 사용해보려고 하는 자세가 부족한 거 같아서
민재를 보고 더 열심히 해야겠다고 생각이 들었습니다!🥰🥰
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저는 스켈레톤 코드를 따로 사용하려고 하지 않았는데 배운 것을 바로바로 응용하는 민재 대단해요..🥰🤭
const handleScroll = useCallback( | ||
throttle(() => { | ||
const container = document.querySelector("#PageContainer"); | ||
if (container) { | ||
const isBottom = | ||
container.scrollHeight - container.scrollTop <= | ||
container.clientHeight + 100; | ||
if (isBottom && !isLoading) { | ||
setPage((prev) => prev + 1); // 페이지 증가 | ||
} | ||
} | ||
}, 300), | ||
[isLoading] // 의존성 배열에 isLoading을 추가하여 해당 값이 변경될 때만 함수 실행 | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
throttle을 사용한 스크롤 이벤트 최적화
useCallback으로 함수 메모이제이션
-> 최적화에 많이 신경 쓰셨네요..!!! 저는 최적화를 고민하지 않았던 거 같은데ㅠㅠ 배워갑니다👍
// 무한 스크롤 감지 (throttle 적용) | ||
const handleScroll = useCallback( | ||
throttle(() => { | ||
const container = document.querySelector("#PageContainer"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
혹시 useRef를 사용하지 않고 document.querySelector를 사용하신 이유가 있을까요?
if (container) { | ||
const isBottom = | ||
container.scrollHeight - container.scrollTop <= | ||
container.clientHeight + 100; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const SCROLL_THRESHOLD = 100; 요런거는 상수로 따로 분리해도 좋을 듯합니다! 저도 하드코딩으로 자주 작성하는데 수정과 재사용을 위해 상수분리하는 습관을 가져야겠더라구요ㅠ
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
안녕하세요, 케이크 WAY 팀의 류원 & 민재 님!
이번 주 코드리뷰를 맡은 송유선입니다. 🤍
민재 님이랑은 정말 자주 매칭되는 것 같아요~~ 늘 코드 리뷰할 때마다 민재 님께서 고민한 흔적들을 느낄 수 있었습니다 ㅎㅎ 다만 이번엔 제가 일정이 바빠 코드를 다운 받아서 돌려볼 수가 없었어서 (ㅜㅜ) 오류의 원인들을 찾지 못하고 그냥 남기기만 했는데, 다음에는 꼭 더 좋은 코드를 위해 수정해서 달아드릴게요
setTimeout(() => { | ||
setResults(movies); // 무작위 영화 목록 저장 | ||
setIsLoading(false); // 로딩 상태 종료 | ||
}, 500); // 0.5초 동안 로딩 상태 유지 (스켈레톤 구현 보이기 위함) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
검색 페이지를 들어가면 스켈레톤 UI가 보였다가 사라지고, 그 뒤에 데이터가 로드되고 있네요!
우선 스켈레톤 UI 너무 잘 구현하셨어요 :) !!
다만 제가 좋은 네트워크 환경에서 접속해도 매번 스켈레톤 UI가 꼭 몇 초간 등장했다가 데이터가 로드되어서 살짝 의아했었거든요. 코드를 봐서 그 이유를 알게 되었어요. 혹시 0.5초의 로딩 상태를 하드코딩하신 있을까요? 사실 스켈레톤 이미지는 데이터가 로딩될 때 사용자 경험을 위해 넣는 거지, 데이터가 빠르게 로딩된다면 데이터를 그냥 빨리 보여주는 게 제일 좋을 것 같아서요! 스켈레톤 UI는 정말 로딩 중일 때만 보이게 하는 방법도 고려해보시면 좋을 것 같아요 ~~
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
사실... 그냥 과제 발표될까 봐 보여... 드리려고 일부러 0.5초로 하드 코딩 했습니다... 하하... 😓😓😓😓
좋은 지적 감사합니다!!!! 유선 님 말씀대로 데이터 로딩이 느린 환경에서 필요한 게 스켈레톤 UI니 데이터가 빠르게 로딩 된다면 빼는 게 맞는 것 같습니다 🔥🔥
setResults([]); // 결과 초기화 | ||
}; | ||
|
||
// 무한 스크롤 감지 (throttle 적용) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
무한 스크롤 구현하신 점 좋아요 👍🏻
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
useMemo를 통해서 최적화하신 점 좋아요~ 다만 지금 홈이나 검색 페이지에서 more, download를 누르면 comming 아이콘이 활성화되고 있습니다..!! 제가 이번에는 클론을 받지 않아 디버깅할 수는 없는데 한 번 확인해보시면 좋을 것 같아요~!
|
||
useEffect(() => { | ||
if (isClicked && audioRef.current) { | ||
audioRef.current.play(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
두둥 소리 구현하신 점 너무 귀엽네요 ㅎㅎ
as={motion.img} | ||
src="/top_10.svg" | ||
alt="" | ||
whileHover={{ scale: 1.2 }} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이건 유저가 클릭할 수 있는 아이콘이 아닌 듯 하여 굳이 hover 시 커지는 애니메이션을 넣지 않아도 될 듯 합니다! 적절한 애니메이션은 사용자 경험을 향상시키지만, 유저 플로우에 맞지 않는 불필요한 애니메이션은 오히려 사용자로 하여금 불편함을 줄 수도 있으니까요~~~
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
애니메이션은 류원 언니가 전담으로 구현해 주셔서 류원 언니께 여쭤보시면 될 듯 싶습니다 🔥🔥 그리고 저도... 눈팅으로 모두 배워가겠습니다... 헤헤
styledComponents: true, | ||
}, | ||
|
||
images: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
next의 images를 활용하신 점 좋아요 ~
"react": "^18.3.1", | ||
"react-dom": "^18.3.1", | ||
"react-lottie-player": "^2.1.0", | ||
"styled-components": "^6.1.13" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
다음에 또 next로 개발하시게 된다면 styled-components 대신 호환성 좋은 tailwind.css를 사용하시는 것을 적극 추천드립니다!
export const metadata: Metadata = { | ||
title: "Netfilx", | ||
description: "Generated by ryuwon and minzae", | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
metadata에는 더 다양한 정보들을 넣을 수 있어요..! title과 description 외에도 keywords, author, robots, canonical, icon 등등.. SEO 최적화를 위해 다음엔 더 다양한 정보를 넣어보신는 건 어떨까요?
import styled, {keyframes} from "styled-components"; | ||
import { AnimatePresence, motion, useInView } from "framer-motion"; | ||
|
||
import play_circle from "../../../public/SearchPage/play-circle.svg"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
상세경로로 간편하게 불러오시는 걸 추천드려요~~
<motion.h2 variants={itemVariants} whileHover={{ scale: 1.1 }}> | ||
Previews | ||
</motion.h2> | ||
<motion.span variants={itemVariants} whileHover={{ scale: 1.1 }}> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아 요거 피그마에 padding-right가 없길래 저도 없앴습니다!!...
💜배포링크💜
💜느낀점💜
이번 과제를 하며…
이번과제에서 디테일페이지와 애니메이션을 구현했습니다.
이번 과제에서 오류가 많이 났던 부분이 디테일페이지 fetch오류인데요, 상대경로라서 자꾸 오류가 났습니다. 다른 fetch함수는 상대경로여도 오류가 나지 않았는데 동적 변수인 movie의 id값을 전달하니 파싱오류가 나서 ㅠ 디테일페이지 fetch함수를 따로 만들어 절대경로로수정하였습니다.
제가 애니메이션을 좋아해서 이번 과제에 애니메이션을 많이 넣어봤습니다. framer-motion 라이브러리를 사용해서 구현했습니다.
나머지는 민재가 구현해줬는데,, 민재 킹갓입니다
이번 과제를 하며… 💫😵💫
이번 과제는 지난 협업을 이어서 해오는 것이라 조금… 아주 조금은 ㅎㅎ 쉬운 한 주가 될 것이라 생각했었는데 막상 시작하니 또 이것저것 할 게 많더군요 ㅜ..ㅜ 큰 코 다칠 뻔 했습니다 😭
저는 이번 과제에서 검색 페이지 구현과 지난 5주차 코드 리뷰를 바탕으로 제가 5주차에 맡았던 부분을 리팩토링을 진행하고 새로운 페이지들의 생성에 따라 BottomNavBar의 경로로 아이콘의 색을 구분하는 로직을 수정했습니다! 또 마지막으로 coming, download, more 버튼을 누르면 로딩 페이지로 연결되게 수정했습니다.
검색 페이지의 UI를 구현하는 건 그리 어렵지 않았지만… 무한 스크롤을 적용하는 것에서 꽤나 애를 먹었습니다 ㅜㅜ api 요청 로직이 조금 이상했어서 자꾸 영화 데이터가 20개씩만 불러와지고, (이때 최대로 20개씩만 영화를 불러오게 하는 TMDB의 정책 탓을 엄청 했었습니다 ㅎㅎ…)
무한 스크롤이 되긴 하지만 useEffect로 감싼 api 요청이 계속 반복돼 페이지가 렌더링 되어 스크롤을 몇 번 내리다 보면 페이지가 리렌더링 되어 상단으로 스크롤이 끌어올려지는 현상을 겪었습니다…
처음에는 이 현상이 api 호출이 아니라 사용자의 스크롤을 감지하는 로직의 문제인 것 같아 그것만 팠는데 어느 순간 네트워크 창을 보니 api가 계속 호출 되어 있던 것을 확인했습니다… 😓😓 (언젠가 어떤 분이 네트워크 창을 잘 보라고 했던 충고가 생각이 나며 이 네트워크 창도 잘 챙겨 봐야겠다는 생각을 했답니답… ) 이를 throttle로 불필요한 계산이나 API 호출을 방지하여 성능을 최적화하여 해결했습니다.
여담으로 이번 과제를 하며 next.js에 대해 아주 쪼금은… 알게 됐나…? 싶다가도 콘솔에 뜨는 에러를 보면 그건 또 아닌 것 같다는 생각이 들었습니다 하하…
그래도넷플릭스 클론코딩을 진행하며 무한 스크롤링에 대해서도 배우고, next.js의 장점(api 키 감출 때 진짜 신기했습니다.. 🥳)들을 명확히 알고 가는 것 같아 정말 성취감이 드는 2주였습니다 😆😆
추가로 류원언니가 애니메이션을 너무 잘 넣어줘서 완전… 완전 만족… 😍😍
💜key question💜
**저희 key question이 너무 길어져서 노션 링크로 첨부하겠습니다! **
🫧노션링크🫧