-
[TIL] 스파르타) JS 개인 과제 피드백 확인 및 수정, Github Release, Semantic VersioningTIL-sparta 2024. 4. 26. 20:05
개인 과제 Github 링크 / 피드백 issue 링크
--
제출했던 개인 과제에 피드백을 주셔서 확인하고 수정하는 과정을 거쳤다.
--
학습 키워드: javascript, html, css, cache, sessionStorage, template literal, github, git, release, version control, semantic versioning
1. window.sessionStorage를 이용한 cache
- 캐싱에 대해서 제대로 배워본 적이 없어서 '임시로 저장해두고 가져다 쓴다' 라는 개념에만 집중해서 스크립트를 작성했기 때문에 미흡하다고 느끼고 있었는데, 이 부분에 대해서 LocalStorage나 SessionStorage를 사용해보라는 피드백을 받았다. 변경점 비교를 위해 기존 버전과 새 버전의 코드를 가져와 봤다.
// 기존 버전 search-movie.js import { addCard, emptyCards } from "./card.js"; import { fetchMoviesList } from "./fetch-movies-db.js"; let cachedList = null; // #2 const searchMoviesByTitle = async (title) => { await setCachedMoviesList(false); let filteredList = cachedList?.filter(function (data) { return data["title"].toLowerCase().includes(title); }); if (!filteredList) return; emptyCards(); title = title.toLowerCase(); // #1 filteredList.forEach(data => { let imageURL = "https://image.tmdb.org/t/p/w300" + data["poster_path"]; addCard(imageURL, data["title"], data["overview"], data["vote_average"], data["id"]); }); } const setCachedMoviesList = async (isLocal) => { if (!cachedList) { cachedList = await fetchMoviesList(isLocal); // console.log("cached new list from " + (isLocal ? "local json." : "TMDB.")); return; } // console.log("Loading cached list"); } const clearCachedList = () => { cachedList = null; // console.log("cleared cachedList"); } export { searchMoviesByTitle, setCachedMoviesList, clearCachedList };
// 수정 버전 search-movie.js import { addCard, emptyCards } from "./card.js"; import { fetchMoviesList } from "./fetch-movies-db.js"; const searchMoviesByTitle = async (title) => { title = title.toLowerCase(); // #1 await setCachedMoviesList(false); // #3 let filteredList = JSON.parse(sessionStorage.getItem("cachedList"))?.filter(function (data) { return data["title"].toLowerCase().includes(title); }); if (!filteredList) return; emptyCards(); filteredList.forEach(data => { let imageURL = "https://image.tmdb.org/t/p/w300" + data["poster_path"]; addCard(imageURL, data["title"], data["overview"], data["vote_average"], data["id"]); }); } const setCachedMoviesList = async (isLocal) => { // #2 if (!window.sessionStorage.getItem("cachedList")) { window.sessionStorage.setItem("cachedList", JSON.stringify(await fetchMoviesList(isLocal))); // console.log("cached new list from " + (isLocal ? "local json." : "TMDB.")); return; } // console.log("Loading cached list"); } const clearCachedList = () => { window.sessionStorage.removeItem("cachedList"); // console.log("cleared cachedList"); } export { searchMoviesByTitle, setCachedMoviesList, clearCachedList };
- 먼저 언급해야할 부분은 기존 버전의 #1인데, 코드를 수정하다가 실수로 뒤로 밀어버리는 바람에 기존에 작동하던 영화 검색의 대소문자 무시 기능이 고장나 있었다. 다른 기능을 테스트 할 때 소문자만 이용하는 바람에 전혀 눈치채지 못했다. 해당 라인을 맨 위로 옮겨서 (수정 버전 #1) 문제를 해결했다.
- 다음은 #2 인데, 기존 버전에서는 네트워크 비용을 낭비하지 않기 위해 DB에서 받아온 영화 목록을 단순히 cachedList 라는 메모리의 변수에 저장해 두었지만, 수정 버전에서는 window 객체의 sessionStorage 에 있는 method들을 이용해 캐싱을 하는 방식으로 변경했다. 위의 MDN 링크를 읽어보니 브라우저에서 새 탭에 문서가 로드될 때 session이 생성되어 해당 탭에 묶이게 되는데, session은 브라우저나 탭이 열려있는 동안에는 계속 유지되어 새로고침이나 페이지 복구 등에서도 계속해서 정보를 유지한다고 한다. 따라서 sessionStorage에 정보를 저장하면 이전처럼 매 새로고침마다 정보가 사라져 DB를 새로 호출하게 되는 불상사가 발생하지 않게 된다.
- 한 가지 유의할 점은 수정 버전의 #3 인데, sessionStorage의 setItem('key', 'value') 메소드는 string 값만 저장한다. 그래서 js의 object를 그대로 추가하면 엉뚱한 값을 저장하게 된다. 이 프로젝트에서 저장할 정보는 json이었으므로 JSON.stringify 함수를 사용해 문자열로 변환하여 저장하고, JSON.parse 와 sessionStorage.getItem('key') 을 이용해 값을 다시 받아오는 방식으로 구현했다.
2. template literal과 유지 보수
- 코드의 유지 보수에 관련된 피드백을 받았다. 마찬가지로 프로젝트의 스크립트를 가져와서 알아보자.
// 기존 버전 card.js let cardContainer = null; // #1 const setCardContainer = (container) => { cardContainer = container ? container : null; } const getCardContainer = () => { return cardContainer; } const createCard = (image, title, overview, rating, id) => { // #2 return ` <div class="cardholder" onclick="alert('영화 id: ${id}')"> <div class="card"> <div class="card-front" style="background-image:url(${image})"> </div> <div class="card-back"> <h1>${title}</h1> <p>${overview}</p> <p>ratings: ${rating}</p> </div> </div> </div> `; } const addCard = (image, title, overview, rating, id) => { if (cardContainer) { // #3 let card = createCard(image, title, overview, rating, id); cardContainer.insertAdjacentHTML('beforeend', card); } } const emptyCards = () => { if (cardContainer) cardContainer.innerHTML = ""; } export { setCardContainer, getCardContainer, createCard, addCard, emptyCards };
// 수정 버전 card.js let $cardContainer = null; // #1 const setCardContainer = ($container) => { $cardContainer = $container ? $container : null; } const getCardContainer = () => { return $cardContainer; } const createCard = (image, title, overview, rating, id) => { // #2 return ` <div class="cardholder"> <div class="card"> <div class="card-front"> <img src="${image}"/> </div> <div class="card-back"> <h1>${title}</h1> <p>${overview}</p> <p>ratings: ${rating}</p> </div> </div> </div> `; } const addCard = (image, title, overview, rating, id) => { if ($cardContainer) { // #3 let card = createCard(image, title, overview, rating, id); $cardContainer.insertAdjacentHTML('beforeend', card); const $cardholder = $cardContainer.lastElementChild; $cardholder.addEventListener("click", (e) => { alert(`영화 id: ${id}`); }); } } const emptyCards = () => { if ($cardContainer) $cardContainer.innerHTML = ""; } export { setCardContainer, getCardContainer, createCard, addCard, emptyCards };
- 먼저 #1은 스크립트의 가독성에 대한 부분이다. 이 부분은 따로 피드백을 받은 것은 아니지만, 얼마 전 강의 과제로 나왔던 작은 스크립트에서 변수/상수들의 이름 앞에 '$'를 붙이는 것을 보고 알아본 내용이다. 이는 DOM element가 변수나 상수에 담겨있음을 알려주는 표시라고 하는데 협업 시 알아보기 쉬워 자주 사용되는 js의 convention이라고 한다.
- 다음 #2는 createCard의 template literal에 관련된 피드백이다. 코드의 유지/보수 관점에서 봤을 때 개발자가 css의 스타일이나 js의 onclick 같은 이벤트를 변경하려면 가장 먼저 확인하게 되는 위치는 css 파일과 js 코드일 것이다. 때문에 기존 버전 처럼 template literal 안에 태그의 attribute를 추가하면 해당 element의 이벤트와 스타일이 흩어져 있어 추후 수정에 어려움이 있을 것이라는게 핵심이다. 그래서 배경 이미지의 경우 img 태그를 사용하고, onclick 이벤트의 경우 수정 버전 #3 에서 처럼 DOM의 addEventListener 메소드로 이벤트를 입혀주는 방법을 사용하여 스크립트나 스타일 작성 위치가 일관성을 유지하도록 수정했다.
3. Github release 와 version control
- 여태껏 github를 사용하면서 단 한번도 써본적이 없는 기능인데, 이번 프로젝트를 진행하면서 쓸 일이 있는지 알아보기 위해 어떻게 사용하는지 찾아보았다.
Release로 새 버전 추가하기:
1) release 하고자 하는 repository 의 메인 화면 우측 사이드 바에서 Releases 를 찾아 클릭한다.
2) 우측 상단에서 Draft a new release 버튼을 찾아 클릭한다.
3) Choose a tag 드롭다운 메뉴를 눌러 release 버전을 찾거나 없다면 새로 입력한다. 이 tag 라는 것도 git의 명령어로 bash에서 추가하거나 제거할 수 있다. 나중에 쓸 일이 생길 때 추가로 알아봐야겠다.
4) 버전 태그를 입력하면 우측에 Generate release notes 버튼이 활성화 되는데, 이 버튼을 누르면 commit history 를 기반으로 자동으로 release note 를 작성해준다.
5) 마지막으로 하단의 Publish release 를 누르면 끝!
- Github의 release 는 API 를 구현한 repository 의 버전 관리를 위해 사용하는 기능인가보다. 이번 프로젝트에는 사용할 이유가 없어서 추후 API를 만들게 될 일이 있는 경우를 대비해 기억만 해두기로 했다.
4. Semantic Versioning
- release 버전의 넘버링에도 convention이 있다. Github에서는 semantic versioning 을 권장하고 있다. 기본적으로 맨 앞에 소문자 v를 붙여주고, v[Major].[Minor].[Patch] 순으로 넘버링한다. 예를들어, 어떤 API의 첫 정규 버전이 두 번의 소규모 기능 업데이트 이후 다섯 번의 버그 수정 패치를 받았다면, v1.2.5 가 되는 식이다. 만약에 이 후에 이 API를 사용하던 유저들에게 Incompatibility 문제를 발생시키는 업데이트가 이루어지는 경우 v2.0.0 으로 맨 앞의 Major 넘버링을 하나 높이고 나머지를 0으로 초기화한다.
- 넘버링의 초기화는 하위 넘버링을 대상으로 이루어진다. 즉, Major 버전 넘버에 변화가 생기면 Minor 와 Patch 의 넘버가 0이되고, Minor 버전의 변화가 있으면 그 하위 넘버인 Patch 가 초기화 되는 식이다.
Major version API 사용에 지대한 영향을 끼치는 변경이 있을 때 ( incompatibility 발생 ) Minor version 기능이 추가됐을 때 ( 해당 기능이 backward compatible 한 경우만 ) Patche version 버그 수정 ( backward compatible 한 경우만 ) 728x90'TIL-sparta' 카테고리의 다른 글
[TIL] 스파르타) CS 강의 수강 (Process) (0) 2024.04.28 [TIL] 스파르타) CS 강의 수강 (CPU와 메모리) (0) 2024.04.27 [TIL] 스파르타) SQL (SELECT) 강의 2~5주차 수강 (2) 2024.04.25 [TIL] 스파르타) JS/SQL 강의 수강, TMDB 프로젝트 수정 (0) 2024.04.24 [TIL] 스파르타) JS문법 종합반 2-4주차 (4-8까지) 수강 (0) 2024.04.23