저번이나 이번 프로젝트 둘 다 나는 html에 작성하던 script를 모듈화 하였다.
그럼 우리가 모듈화 하는 이유는 뭘까?
가장 중요한 첫번째 이유는 "코드의 재사용성"이다.
가령 우리가 a와 b를 더해주는 함수를 사용할 때마다 만든다고 가정해보자.
function a() {
function add(a,b) {
return a+b
}
console.log(add(1,2))
}
function b() {
function add(a,b) {
return a+b
}
console.log(add(1,2))
}
function c() {
function add(a,b) {
return a+b
}
console.log(add(1,2))
}
한눈에 봐도 더러운 코드가 되고, 재사용성이 떨어지며, 유지보수가 어렵게 느껴진다.
여기서 add의 기준을 a + b + 1을 하는 행동으로 바꿔야한다면, 세 함수 모두 하드코딩해야 한다는 것이다.
따라서 우리는 add라는 함수를 따로 선언해두고, 필요한곳마다 호출하여 사용하기위해 모듈화를 하는 것이다.
function add(a,b) {
return a+b+1
}
function a() {
console.log(add(1,2))
}
function b() {
console.log(add(1,2))
}
function c() {
console.log(add(1,2))
}
얼마나 깔끔한가?
코드 줄수도 매우 줄었을 뿐더러 명확하게 어떤 행동을 하는지 알 수 있다.
따라서 우리는 모듈화를 결정할 때 제일 먼저, 해당 함수가 재사용되는지 확인한다.
그리고 그 함수들을 적당히 분류하여(api요청끼리, addEvent끼리, makeBox끼리...) 한 파일안에서 각각을 export해주는 것이다.
그런 함수뭉치파일에서 원하는 함수만 적절한 때 import하여 사용하는것이 올바르다.
또한 그렇게 함수자체를 만들어두면 위처럼 어떤 동작을 동일하게 변경해야할 때 처리하기도 편하다.
두번째로 한 함수내에서 모듈화를 하는 것은
export async function makeDetailBox(response) {
const $detailContainer = document.querySelector(".detailContainer");
const { adult, genres, id, overview, release_date, runtime, title, vote_average, poster_path, backdrop_path } =
response;
$detailContainer.innerHTML = `
<div>
<div>${adult}</div>
<div>${genres}</div>
<div>${id}</div>
<div>${overview}</div>
<div>${release_date}</div>
<div>${runtime}</div>
<div>${vote_average}</div>
</div>
`
}
이런 디테일컨테이너에 어떤 변수들을 적절히 넣는 작업을 한다고 가정해보자.
여기서는 코드가 짧고 템플릿 리터럴의 모양이 간결하여 괜찮지만, 현실은 그렇지않다.
div태그안에 p태그안에 span태그안에 title을 넣어주고, div태그안에 p태그안에 genres를 넣어주고.....
를 반복하다보면 저 템플릿리터럴 속 요소들이 정말 보기힘들정도로 더러워진다.
따라서 우리는 저 innerHTML을 지정해주는 것을 모듈화 하여 코드의 직관성을 높여야할 필요가 있다.
이것이 모듈화의 두번째 필요성이다.
function setDetailContainer(adult, genres, id, overview, release_date, runtime, vote_average) {
$detailContainer.innerHTML = `
<div>
<div>${adult}</div>
<div>${genres}</div>
<div>${id}</div>
<div>${overview}</div>
<div>${release_date}</div>
<div>${runtime}</div>
<div>${vote_average}</div>
</div>
`
}
export async function makeDetailBox(response) {
const $detailContainer = document.querySelector(".detailContainer");
const { adult, genres, id, overview, release_date, runtime, title, vote_average, poster_path, backdrop_path } =
response;
setDetailContainer($detailContainer, adult, genres, id, overview, release_date, runtime, vote_average)
}
이렇게 makeDetailBox가 간결하고 어떤 행동을 하는지 직관적으로 파악이 가능해진다.
다만 어디서나 그렇지만, 모듈화한 함수의 네이밍을 정말 잘해야한다.
여기서처럼 setDetailContainer을 보면 아~ 디테일컨테이너를 세팅하는 함수구나, 이런 인자들이 들어가는구나 하며 어느정도 느낌이 온다.
그리고 자세히 알고싶으면 해당 함수를 보고 어떻게 동작하는건지 알면 되는 것이다.
아래 코드가 오늘 모듈화를 진행했던 코드중 하나이다.
import { getActorList, getMovieTrailer, getSimillarMovies } from "./getApiRequests.js";
import { getStarWidth, getGenresString, getruntime } from "./util.js";
import { YOUTUBE_API_KEY } from "../apiOptions.js";
//예고편을 추가하는 함수
function addTrailerContainer(trailerKey, trailerContainer) {
//예고편을 담을 iframe 생성
const $trailer = document.createElement("iframe");
//iframe 속성 설정
Object.assign($trailer, {
id: `${trailerKey}`,
style: "transition: all 0s linear;",
src: `https://www.youtube.com/embed/${trailerKey}`,
title: "YouTube video player",
frameborder: "0",
allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen"
});
//마우스 오버 이벤트를 작동시킬 최소너비 지정
const responsiveWidth = matchMedia("screen and (min-width: 1500px)");
//마우스 오버시 크기 변화
$trailer.addEventListener("mouseover", () => {
//최소너비 이상일때만 작동
if (responsiveWidth.matches) {
$trailer.style.transform = "scale(1.1)";
}
});
//마우스 아웃시 크기 원래대로
$trailer.addEventListener("mouseout", () => {
$trailer.style.transform = "scale(1.0)";
});
//예고편을 담을 요소에 추가
trailerContainer.appendChild($trailer);
}
function setDocumentTitle(title) {
document.title = title;
}
function setBackgroundPoster(backdrop_path) {
const $backgroundPoster = document.getElementById("backgroundPoster");
Object.assign($backgroundPoster.style, {
backgroundImage: `url(https://image.tmdb.org/t/p/w500${backdrop_path})`,
backgroundSize: "cover",
backgroundPosition: "top",
filter: "blur(15px)",
width: "calc(100vw - 17px)",
height: "100vh",
position: "absolute",
zIndex: "-1"
});
}
function setPoster(poster_path) {
//포스터 이미지 생성
const $moviePoster = document.createElement("img");
$moviePoster.className = "moviePoster";
//포스터 이미지가 서버에 있을경우 포스터 이미지를 불러오고 없을경우 대체이미지를 불러옴
$moviePoster.src = poster_path
? `https://image.tmdb.org/t/p/w500${poster_path}`
: "../../Asset/posterPlaceholder.png";
return $moviePoster;
}
function setMovieDetailBox($movieDetailBox, title, starWidth, release_date, adult, runtime, genresString, overview) {
$movieDetailBox.innerHTML += `
<h1 class="movieTitle">${title}</h1>
<div class="movieVote">
<p class='blankStar'>☆☆☆☆☆</p><p style='width: ${starWidth}%' class='filledStar'>★★★★★</p>
</div>
<p class="movieDesc">
<span class="movieReleaseDate">${release_date.slice(
0,
4
)} </span> / <span class="movieRating">${adult ? "19+" : "15+"}</span> /
<span class="movieRunningTime">${getruntime(runtime)}</span> /
<span class="moviegenres">${genresString}</span>
</p>
<p class="movieOverview">
${overview}
</p>`;
}
// 디테일 박스를 만드는 함수
export async function makeDetailBox(response) {
const $detailContainer = document.querySelector(".detailContainer");
//영화 상세정보에서 필요한 정보 추출
const { adult, genres, id, overview, release_date, runtime, title, vote_average, poster_path, backdrop_path } =
response;
//페이지 타이틀 변경
setDocumentTitle(title);
//배경 이미지 처리
setBackgroundPoster(backdrop_path);
//장르 배열 문자열화
const genresString = getGenresString(genres);
//평점에 따라 별이 채워지도록 계산
const starWidth = getStarWidth(vote_average);
//상세정보를 담을 요소 생성
const $detailBox = document.createElement("div");
$detailBox.className = "detailBox";
//포스터 이미지를 상세정보를 담아줄 요소에 추가
$detailBox.appendChild(setPoster(poster_path));
//영화 상세정보를 담아줄 요소 생성
const $movieDetailBox = document.createElement("div");
$movieDetailBox.className = "movieDetailBox";
//영화 상세정보를 상세정보를 담아줄 요소에 추가
$detailBox.appendChild($movieDetailBox);
//영화 상세정보를 적당한 템플릿 리터럴 규격에 맞춰 추가
setMovieDetailBox($movieDetailBox, title, starWidth, release_date, adult, runtime, genresString, overview);
//상세정보를 담아줄 요소에 추가
$detailContainer.appendChild($detailBox);
//예고편을 담아줄 요소 생성
let $trailerContainer = document.createElement("div");
//예고편을 담아줄 요소 스타일 지정
$trailerContainer.className = "trailerContainer";
$trailerContainer.id = "trailerContainer";
Object.assign($trailerContainer.style, {
marginTop: "10px",
display: "flex",
flexDirection: "row",
gap: "5px",
width: "inherit",
padding: "10px 0px",
overflowX: "auto"
});
//예고편 데이터를 getMovieTrailer함수를 통해 받아옴
let trailerData = await getMovieTrailer(id);
//예고편 데이터 중 타입이 Trailer인 것만 필터링
trailerData = trailerData.filter((item) => item.type === "Trailer");
//예고편 데이터가 있을경우 예고편을 추가
if (trailerData.length > 0) {
//예고편 데이터를 순회하며 예고편을 추가
trailerData.forEach(async (trailer) => {
//예고편의 키값 추출
const trailerKey = trailer.key;
//예고편의 키값과 youtubeAPIKEY를 이용해 youtubeAPI에 상세정보 요청할 주소 생성
const YOUTUBE_API_URL = `https://www.googleapis.com/youtube/v3/videos?id=${trailerKey}&key=${YOUTUBE_API_KEY}&part=contentDetails,status`;
//상세정보 요청
await fetch(YOUTUBE_API_URL)
.then((res) => res.json())
.then((data) => {
//예고편의 임베드가능여부, 공개여부, 지역제한여부 확인
const { embeddable, privacyStatus } = data.items[0].status;
const { regionRestriction } = data.items[0].contentDetails;
let regionFlag = true;
//지역제한이 있고 한국이 허용지역에 없을경우 예고편 추가하지 않음
if (regionRestriction && !regionRestriction.allowed.some((el) => el === "KR")) {
regionFlag = false;
}
//임베드가능여부가 true이고 공개여부가 public이며 지역제한이 없을경우 예고편 추가
if (embeddable === true && privacyStatus === "public" && regionFlag) {
//예고편을 추가하는 함수 호출
addTrailerContainer(trailerKey, $trailerContainer);
}
});
});
//예고편을 담아줄 요소를 상세정보를 담아줄 요소에 추가
$movieDetailBox.appendChild($trailerContainer);
}
//예고편 로딩이 끝나면 로딩화면을 숨김
const $dotWrapper = document.querySelector(".dotWrapper");
$dotWrapper.setAttribute("style", "display: none");
//로딩이 끝나면 댓글과 등장인물탭이 나타나도록
const $commentContainer = document.getElementById("commentContainer");
$commentContainer.style.display = "flex";
const $actorList = document.getElementById("actorList");
$actorList.style.display = "flex";
//비슷한 영화를 불러오는 함수 호출
getSimillarMovies(title);
//배우 목록을 불러오는 함수 호출
getActorList(title);
}
모듈화를 해서 나눠도 이정도 길이인데, 아니었을때 makeDetailBox함수가 얼마나 길었고 얼마나 보기 흉했을지 상상이라도 가는지
정말 눈뜨고 볼 수 없는 더러움이었다.
10년동안 제자리에있던 책장을 옮겼을 때 쌓여있던 먼지마냥 더러웠다.
그나마 깨끗해진것이다...
어쨋든 모듈화를 진행할 때는
1. 코드의 재사용이 있는가?
2. 한 파일에서 특정 함수를 직관적으로 간결하게 표현할 필요가 있는가?
를 생각하여 모듈화를 진행해야한다. 안그러면 정말 남이 보기도 힘들지만 내가 해석하는데도 오래걸린다.
'2차 공부 > TIL' 카테고리의 다른 글
24.08.08 리액트 입문주차 시작 (0) | 2024.08.08 |
---|---|
24.08.07 자바스크립트 팀프로젝트 회고 (0) | 2024.08.07 |
24.08.05 팀프로젝트 troubleshooting (0) | 2024.08.05 |
24.08.02 js 팀프로젝트 진행상황 / troubleshooting (0) | 2024.08.02 |
24.08.01 유튜브 영상을 웹 페이지 (0) | 2024.08.01 |