1. axios baseUrl
import axios from "axios";
const instance = axios.create({
baseURL: "https://api.themoviedb.org/3",
params: {
api_key: "e8a960ccca46e3b7c8974d7353cbfadc",
language: "ko-KR",
},
});
export default instance;
2. requests.js
const requests = {
fetchNowPlaying: "movie/now_playing",
fetchNetflixOriginals: "/discover/tv?with_networks=213",
fetchTrending: "/trending/all/week",
fetchTopRated: "/movie/top_rated",
fetchActionMovies: "/discover/movie?with_genres=28",
fetchComedyMovies: "/discover/movie?with_genres=35",
fetchHorrorMovies: "/discover/movie?with_genres=27",
fetchRomanceMovies: "/discover/movie?with_genres=10749",
fetchDocumentaries: "/discover/movie?with_genres=99",
};
export default requests;
템플릿 문자열 일때 요청객체를 따로 빼는 방법
export const requests = (state, data = undefined) => {
const requests1 = {
getPosts: `community/${state?.posts.NowCommunityId}/${
state?.posts.CommunityState
}/${100}/posts`,
setPost: `community/${state?.posts.NowCommunityId}/post`,
setComment: `/community/${data?.id}/comment`,
setReply: `/community/${data?.refId}/refcomment`,
getPostDetail: `/community/${data?.postId}/post`,
};
return requests1;
};
다음처럼 첫번째 인자를 아무것도 안넣어주기 위해서 undefined 를 전달하였다.
await axios.post(requests(undefined, data).setComment, data.res);
useState, useEffect 등 snippet 사용
4. addEventListener
useEffect(() => {
window.addEventListener("scroll", () => {
console.log("window.scrollY", window.scrollY);
if (window.scrollY > 50) {
setShow(true);
} else {
setShow(false);
}
});
return () => {
window.removeEventListener("scroll", () => {});
};
}, []);
5. 템플릿 문자열을 활용한 클래스 추가
<nav className={`nav ${show && "nav__black"} `}>
6. 비동기 요청시 async await / 만들어 두었던 requests 객체의 사용
const fetchData = async () => {
// 현재 상영중인 영화 정보를 가져오기(여러 영화)
const request = await axios.get(requests.fetchNowPlaying);
}
7. Random
1~20 까지의 랜덤 수를 뽑고 싶다면, Math.random() = 20으로 할당하면 된다.
Math.floor(Math.random() * request.data.results.length)
8. 구조분해 할당 이름바꿔서 받기
const { data: movieDetail } = await axios.get(`movie/${movieId}`, {
params: { append_to_response: "videos" },
});
9. backgroundImage 는 인라인 으로 주기
<header
className="banner"
style={{
backgroundImage: `url("https://image.tmdb.org/t/p/original/${movie.backdrop_path}")`,
backgroundPosition: "top center",
backgroundSize: "cover",
}}
>
10. 특이한 or 문
movie.title 이 있으면 우선으로 movie.title 이 들어가게 된다.
movie.title -> movie.name -> movie.original_name 순이다.
<h1 className="banner__title">
{movie.title || movie.name || movie.original_name}
</h1>
11. 말 줄임 표시
const truncate = (str, n) => {
return str?.length > n ? str.substr(0, n - 1) + "..." : str;
};
<h1 className="banner__description">{truncate(movie.overview, 100)}</h1>
12. 동영상을 넣는 요소 Iframe
<Iframe
width="640"
height="360"
src={`https://www.youtube.com/embed/${movie.videos.results[0].key}?controls=0&autoplay=1&loop=1&mute=1&playlist=${movie.videos.results[0].key}`}
title="YouTube video player"
frameborder="0"
allow="autoplay; fullscreen"
allowfullscreen
></Iframe>
13. Iframe src 에서 loop , mute 등 설정 가능
src={`https://www.youtube.com/embed/${movie.videos.results[0].key}?controls=0&autoplay=1&loop=1&mute=1&playlist=${movie.videos.results[0].key}`}
14. props 를 넘겨주는 방법
다음과 같이 isLargeRow 로 넘겨주게되면 props true 로 넘어가게 된다.
<Row
title="NETFLIX ORIGINALS"
id="NO"
fetchUrl={requests.fetchNetflixOriginals}
isLargeRow
></Row>
15. < 화살표를 적어주고 싶을때는
다음과 같이 { "<" } 를 사용한다. 왜냐하면 < 과 충돌이 되기 때문이다.
<span className="arrow">{"<"}</span>
16. 템플릿 문자열의 중요성 삼항연산자로 url
<img
key={movie.id}
className={`row__poster ${isLargeRow && "row__posterLarge"}`}
src={`https://image.tmdb.org/t/p/original/${
isLargeRow ? movie.poster_path : movie.backdrop_path
} `}
alt={movie.name}
// onClick={() => handleClick(movie)}
/>
17. transform을 사용한 인터랙티브
다음처럼 transition과 transform을 사용한 효과를 줄 수 있다.
.row__poster {
object-fit: contain;
width: 100%;
max-height: 144px;
margin-right: 10px;
transition: transform 450ms;
border-radius: 4px;
}
.row__poster:hover {
transform: scale(1.08);
}
17. innerWidth, innerHeight 의 정의
18. innerWidth 를 사용한 scroll
onClick={() => {
document.getElementById(id).scrollLeft -= window.innerWidth - 80;
}}
19.props 한방에 내려주기
<MovieModal {...movieSelected} setModalOpen={setModalOpen} />
20. 모달에서 스크롤을 감추는 방법
.modal::-webkit-scrollbar {
display: none;
visibility: hidden;
}
/* Hide scrollbar for IE, Edge and Firefox */
.modal {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
21. 모달이 두둥 하고 열리는 방법
animation: fadeIn 과 transition의 조합 그리고 keyframes
.modal {
position: relative;
max-width: 800px;
box-shadow: 0px 3px 5px -1px rgba(0, 0, 0, 0.2),
0px 5px 8px 0px rgba(0, 0, 0, 0.14), 0px 1px 14px 0px rgba(0, 0, 0, 0.12);
background: #111;
overflow: hidden;
border-radius: 8px;
transition: all 400ms ease-in-out 2s;
animation: fadeIn 400ms;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: scale(0.5);
}
to {
opacity: 1;
transform: scale(1);
}
}
추가적으로 keyframes 는 다음과 같이 %를 사용해서 중간 애니메이션도 추가해줄수가 있다.
@keyframes fadeIn {
from {
opacity: 0;
transform: scale(0.5);
}
50% {
transform: scale(0.1);
}
to {
opacity: 1;
transform: scale(1);
}
}
22. inset 에 대한 이해
다음과 같은 css 코드가 있었는데, inset 이라는 css 가 궁금했다.
mdn 을 찾아보면, padding: 0px 0px 0px 0px 과 같이 top , right, bottom, left 을 4방향으로 값을 줄 수 있는 것이였다.
.wrapper-modal {
position: fixed;
inset: 0px;
background-color: rgb(0 0 0 / 71%);
-webkit-tap-highlight-color: transparent;
display: flex;
justify-content: center;
}
그렇다면 저걸 사용하지않고 top:0px, left:0px 만 줘서 inset 과 동일한 동작을 해야했는데 그렇지 않았다.
이유는 width 와 height 값을 주지 않아서였다. 다음과 같이 해야된다.
.wrapper-modal {
position: fixed;
/* inset: 0px; */
top: 0px;
left: 0px;
width: 100vw;
height: 100vh;
background-color: rgb(0 0 0 / 71%);
-webkit-tap-highlight-color: transparent;
display: flex;
justify-content: center;
}
23. react-router-dom
react-router-dom 에서 중첩 라우팅에 대해 배웠다.
다음과 같은 코드가 있을때 중첩라우팅을 사용할 수 있다.
index 는 default 즉 '/' 의 주소로 접근했을때의 컴포넌트를 의미한다.
element가 <Layout/>인 라우트로 감싸져있다. 우선 Layout 컴포넌트를 보자.
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<MainPage />} />
<Route path=":movieId" element={<DetailPage />} />
<Route path="search" element={<SearchPage />} />
</Route>
</Routes>
</BrowserRouter>
다음과 같이 코드가 짜여있는데 <Outlet/> 을 사용해서 중첩라우트에서 하위 컴포넌트를 사용할 수 있는 것이다.
저 Outlet 자리에 MainPage가 들어간다고 생각하면 된다.
const Layout = () => {
return (
<>
<Nav />
<Outlet /> /* <MainPage/> */
<Footer />
</>
);
};
26. 중앙에 element를 두는 방법
다음과 같은 코드이다. transform의 translate(-50%,0)을 사용하는 코드.
이 코드를 보고 갑자기 든 생각인데
.nav__input {
position: fixed;
left: 50%;
transform: translate(-50%, 0);
background-color: rgba(0, 0, 0, 0.582);
border-radius: 5px;
color: white;
padding: 5px;
border: none;
}
다음과 같은 UI를 구현하기 위해서 justify-content : space-around 를 사용했던 것 같다.
그런데 생각을 해보니 position: absolute로 주고 위처럼 각각 left:10% , left: 50% , left: 90%로 주고 transform: transelate(50%,0)을 사용해서도 구현할 수 있었지 않을까? 라는 생각이 든다.
27. useNavigate()
useNavigate()를 사용하면 해당 url로 이동할 수 있다.
const [searchValue, setSearchValue] = useState("");
const navigate = useNavigate();
const handleChange = (e) => {
setSearchValue(e.target.value);
navigate(`/search?q=${e.target.value}`);
};
<input
value={searchValue}
onChange={handleChange}
className="nav__input"
type="text"
placeholder="영화를 검색해주세요."
/>
28. useLocation
useLocation에는 다음과 같은 정보가 담겨있다.
pathname 에는 현재 라우트 경로인 /search 가 담겨있고, search에는 라우트 경로의 뒤에 붙는 문자열이 담겨져 있다.
29. search 에 담긴 정보 parsing 하는 방법
다음과 같은 코드를 사용하면 된다. q 일때 말고 p 라면 어떨까?
const useQuery = () => {
return new URLSearchParams(useLocation().search);
};
let query = useQuery(); // URLSearchParams {}
const searchTerm = query.get("q"); // ?q= 뒤의 문자열
만약에 ?p= 일때 query.get("p")를 사용하여도 똑같이 작동하였다.
30. useEffect 를 활용한 axios 요청
다음 처럼 useEffect 에 axios 요청함수를 넣어서 사용할 수도 있었다.
미리알았다면 이렇게 로직을 짤 수 있었을 텐데..
근데 이대로라면 searchTerm 이 계속 바뀌기 때문에 요청이 계속가서 서버에 무리를 줄 수 있을 것 같다고 생각한다.
아마 다음강의에서 바꾸겠지?
useEffect(() => {
if (searchTerm) {
fetchSearchMovie();
}
}, [searchTerm]);
const fetchSearchMovie = async (searchTerm) => {
console.log("searchTerm", searchTerm);
try {
const request = await axios.get(
`/search/multi?include_adult=false&query=${searchTerm}`
);
console.log(request);
setSearchResults(request.data.results);
} catch (error) {
console.log("error", error);
}
};
31.
.movie {
flex: 1 1 auto;
display: inline-flex;
padding-right: 0.5rem;
padding-bottom: 7rem;
}
display: inline-flex를 사용해서 inline-block 과 같은 효과를 낼 수 있다.
32. 함수에 JSX 를 담아서 리턴
다음처럼 함수에 JSX를 담아서 리턴할 수 있다는 것을 알게 되었다.
const renderSearchResults = () => {
return searchResults.length > 0 ? (
<section className="search-container">
{searchResults.map((movie) => {
if (movie.backdrop_path !== null && movie.media_type !== "person") {
const movieImageUrl =
"https://image.tmdb.org/t/p/w500" + movie.backdrop_path;
return (
<div className="movie" key={movie.id}>
<div className="movie__column-poster">
<img
src={movieImageUrl}
alt="movie"
className="movie__poster"
/>
</div>
</div>
);
}
})}
</section>
) : (
<section className="no-results">
<div className="no-results__text">
<p>찾고자하는 검색어"{searchTerm}"에 맞는 영화가 없습니다.</p>
</div>
</section>
);
};
return renderSearchResults();
33. 디바운스란?
- 일정 시간내에 연이어 호출되는 함수들을 하나로 취급해 가장 마지막것만 실행시키는 것.
- 스로틀링 - 함수를 실행한 순간부터 일정 시간동안 다시 호출되지 않도록 하는 것.
34. react custom hooks
리액트에서 커스텀 훅을 만들때 useHooks 와 같이 앞에 use를 붙혀주어야한다.
처음에 이 훅을 봤을때, 이런생각이 들었다. '디바운스라는건 딜레이를 줘서 여러번의 요청이 가지 않도록 컨트롤 하는 것인데, 어떻게 이걸로 그게 가능하지?'
가능했다 왜냐하면 useEffect 안에 있기 때문이다. 시뮬레이션을 해보자.
1. value가 들어가서 setTimeout이 실행된다. delay 1초이다.
2. 1초이전에 value가 업데이트 되어서 기존의 setTimeout이 clearTimeout이 되어버렸다.
그러면 이전값은 debounceValue에 영향을 끼칠 수 없게 되는 것이다.
import { useState, useEffect } from "react";
export const useDebounce = (value, delay) => {
const [debounceValue, setDebounceValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebounceValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debounceValue;
};
34. 추가 생각
다음과 같은 커스텀훅이 있다고 하자.
import { useState, useEffect } from "react";
export const useTest = () => {
const [state, setStete] = useState("dddd");
return [state];
};
검색을 해서 커스텀훅의 리턴형을 보았는데 대부분 저런식으로 [] 배열형으로 반환하더라. 왜 그런지 궁금했다.
이유는 리턴 값이 하나일때는 return state 와 return [state] 가 차이가 없겠지만, 리턴형이 2개이상이라고 생각해보자.
그렇다면 return 을 [state1,state2] 로 해야 구조분해할당으로 const [a,b] = useTest()로 받을 수 있기때문이다.
35. useParam
useParam의 기능은 다음과 같다.
:movieId 의 값을 가져올 수 있는 것이다.
현재 상위 라우트가 "/" 이므로 localhost:3000/131833 과 같은 url 에서 131833의 값을 얻을 수 있게 된다.
코드는 다음과 같다.
movieId 인 이유는 react-router-dom 에서 지정한 path :movieId 때문이다.
import { useParams } from "react-router-dom";
import axios from "../../api/axios";
export default function DetailPage() {
const { movieId } = useParams();
...
}
36. 모달 영역 바깥에 클릭시 모달 닫히도록 구현
다음과 같은 커스텀훅을 만들어준다.
import { useEffect } from "react";
export default function useOnClickOutside(ref, handler) {
useEffect(() => {
const listener = (event) => {
console.log(event.target);
console.log(ref.current);
if (!ref.current || ref.current.contains(event.target)) {
return;
}
handler();
};
document.addEventListener("mousedown", listener);
document.addEventListener("touchstart", listener);
return () => {
document.removeEventListener("mousedown", listener);
document.removeEventListener("touchstart", listener);
};
}, [ref, handler]);
}
모달에 ref 를 커스텀 훅에 넘겨준다. 그리고 setModalOpen(false)로 만드는 익명함수도 넘겨준다.
useOnClickOutside(ref, () => {
setModalOpen(false);
});
return (
...
<div className="modal" ref={ref}></div>
...
)
ref.current.contains(event.target) 은 다음과 같이 동작한다.
event.target이 모달 내부의 DOM element 이기때문에, 모달 전체 element에 포함이 될 것이다.
그것을 사용하여 참/거짓을 판별하는 로직이다.
if (!ref.current || ref.current.contains(event.target)) {
return;
}
37. slider 구현 , swiper 를 사용
나중에 slider 를 구현해야할 일이 있다면 다음 공식문서를 읽어보기를 바란다.
https://swiperjs.com/react#effects
swiper 코드
<Swiper
// install Swiper modules
modules={[Navigation, Scrollbar, A11y]}
loop={true} // loop 기능을 사용할 것인지
breakpoints={{
1378: {
slidesPerView: 6, // 한번에 보이는 슬라이드 개수
slidesPerGroup: 6, // 몇개씩 슬라이드 할지
},
998: {
slidesPerView: 5,
slidesPerGroup: 5,
},
625: {
slidesPerView: 4,
slidesPerGroup: 4,
},
0: {
slidesPerView: 3,
slidesPerGroup: 3,
},
}}
navigation // arrow 버튼 사용 유무
// pagination={{ clickable: true }} // 페이지 버튼 보이게 할지
>
<div id={id} className="row__posters">
{movies.map((movie) => (
<SwiperSlide>
<img
key={movie.id}
className={`row__poster ${isLargeRow && "row__posterLarge"}`}
src={`https://image.tmdb.org/t/p/original/${
isLargeRow ? movie.poster_path : movie.backdrop_path
} `}
alt={movie.name}
onClick={() => handleClick(movie)}
/>
</SwiperSlide>
))}
</div>
</Swiper>
이미 구현되어있는 라이브러리의 css 를 변경하기 위해서는 !important 를 사용하면 된다.
다음과 같이 말이다.
.swiper-pagination {
text-align: right !important;
}
38. Github 배포
'공부기록 > 웹 개발' 카테고리의 다른 글
javascript super (0) | 2022.08.24 |
---|---|
ios 스크롤 바운스 (0) | 2022.08.16 |
첫 해커톤 참여후기 [놀다가는 앞마당 해커톤] (4) | 2022.08.16 |
동적 데이터요소 style 주기 (0) | 2022.08.11 |
리액트 새로고침 시 대응방법 (0) | 2022.08.08 |