하루 하나씩 작성하는 TIL #14


어제 작성하였던 localStorage 코드에 대해 리뷰.


localStorage란?

로컬 스토리지는 브라우저 내에서 영구적으로 데이터를 저장하는 데 사용된다.

저장한 데이터는 브라우저를 종료하거나 컴퓨터를 재시작해도 유지된다.

키와 값을 기반으로 배열로 저장한다


다른 분이 제작해주실 페이지에 삽입해야 할 코드이지만, js만 짜놓으면 정상적으로 동작하는지의 여부를 정확하게 알 수 없기 떄문에 간단하게 html틀을 짜놓았다.

html class명 체크

<!DOCTYPE html>
<html>

<head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta charset="UTF-8">
    <title>영화 리뷰 작성</title>
    <link rel="stylesheet" href="styles.css">
</head>

<body>
    <section>
        <form class="js-reviewForm">
            <div class="left-input">
                <input class="user-id-input" type="text" placeholder="사용자 아이디" /><br>
                <input class="password-input" type="password" placeholder="비밀번호 (4자리 이상)" />
            </div>
            <div class="right-input">
                <input class="review-input" type="text" placeholder="리뷰를 작성해주세요" />
            </div>
            <button type="submit">작성하기</button>
        </form>
        <ul class="js-reviewList"></ul>
    </section>

    <script src="script.js"></script>
</body>

</html>

js-reviewForm

left-input

user-id-input

password-input

right-input

review-input

js-reviewList

class명은 각각 이렇게 준 상태
left right는 원래 각각 좌우로 배치하려다 별로여서 세로로 놨습니다... 어차피 제 코드는 프로젝트에서 js만 사용될 예정이기 때문에 class명 양해 부탁합니다...


1. 변수 선언

const reviewForm = document.querySelector(".js-reviewForm");
const reviewInput = reviewForm.querySelector(".review-input");
const userIdInput = reviewForm.querySelector(".user-id-input");
const passwordInput = reviewForm.querySelector(".password-input");
const reviewList = document.querySelector(".js-reviewList");
//html문서에서 해당 클래스들 가진 요소를 선택하기 위한 변수들

일단, js에서 해당 클래스를 가진 요소를 선택하기 위한 변수들을 선언해준다.


const REVIEWS_LS = "reviews";
//로컬 스토리지에 리뷰 데이터를 저장할 때 사용할 키 값
let reviews = [];
//리뷰 데이터를 담을 배열을 선언

로컬 스토리지는 키 값 쌍 으로 데이터를 저장하기 때문에 키 값을 정해줘야 한다.
reviews는 실제로 데이터를 담을 배열이다.

(꼭 배열이 아니여도 되지만 필자는 배열이 편해서 사용하였다.)


2. 리뷰 제출 후 함수

가장 먼저 실행될 순서인 리뷰 작성하기 버튼 클릭 후 폼이 submit 되었을 때의 동작 코드를 짜준다.


//제출되었을 때 호출 함수
function handleSubmit(event) {
  event.preventDefault();
  //기본 제출 동작 막기. 폼이 서버로 제출되는 것을 방지, js에서 정의한 동작 실행
  const currentReview = reviewInput.value;
  const currentUserId = userIdInput.value;
  const currentPassword = passwordInput.value;

제출 동작을 해줄 handleSubmit 함수를 정의해준다.

event는 js에서 이벤트가 발생하면, 이벤트에 대한 정보를 가지고 있는 객체가 생성이 된다. 이 객체는 해당 이벤트에 대한 다양한 정보와 메서드가 포함되어 있다.

Q. event.preventDefault()를 써줘야 하는 이유?

submit과 같은 이벤트의 경우, 폼이 제출되면 페이지가 다시 로드되며 서버로 데이터가 전송이 되는데, 이 동작은 웹페이지를 다시 로드하고 페이지 상태를 변경하는데 사용된다.

하지만 위 코드를 호출하면 기본 동작이 막히게 된다
= 폼이 제출되어도 페이지가 리로드 되지 않고 js에서 정의된 동작만 실행된다.
= 페이지를 새로 고치지 않고도 폼 제출 시 즉시 응답이 가능해진다.

선언된 변수들은 각각 리뷰 내용, id, 비밀번호를 가져와 변수에 할당해준다.


if (currentReview === "" || currentUserId === "" || currentPassword === "") {
    alert("모든 필드를 입력하세요.");
    return;
  }

  if (currentPassword.length < 4) {
    alert("비밀번호는 4자리 이상이어야 합니다.");
    return;
  }

입력 받은 값에 대한 유효성 검사도 해준다.



이 과정까지 끝나면, 작성한 리뷰가 하단에 뜨도록 해줘야한다. 하지만 아직 제출 동작에 대한 handleSubmit()함수만 정의해 주었고, 리뷰 표시에 대한 함수를 정의해주지 않았다. 일단 리뷰 표시 함수를 간단하게만 정의해서 이 함수 내가 아닌 외에 정의해준다.

function paintReview() {
}

일단은 submit 버튼 클릭시 하단에 작성한 리뷰가 떠야하니까 이렇게만 정의해주고, 마저 이어서 handleSubmit()을 작성하자면,

나머지 코드들은

paintReview(currentReview, currentUserId, currentPassword);
  //입력된 리뷰 내용과 사용자 정보를 기반으로 함수를 호출하여 새로운 리뷰를 화면에 추가
  reviewInput.value = "";
  userIdInput.value = "";
  passwordInput.value = "";
}

현재 입력받은 값을 paintReview를 통해 출력하는 방식으로 마무리를 할 수 있을 거 같다.

출력 후에는 입력 필드의 값을 위와 같이 비워준다.


3. 리뷰 저장 함수

리뷰가 입력되었으면 로컬 스토리지에 값이 저장되어야한다. 이에 대한 리뷰 저장 함수이다.

function saveReviews() {
  localStorage.setItem(REVIEWS_LS, JSON.stringify(reviews));
}

localStorage.setItem(key, value) 함수는 로컬 스토리지에 데이터를 저장하는 데 사용된다. 이 함수는 key와 value 두 가지 매개변수를 사용한다.

REVIEWS_LS는 이전에 정의 된 상수이다.

로컬 스토리지에 저장될 리뷰 데이터의 키, 배열인 reviews를 json 문자열로 변환해준다. 로컬 스토리지에는 문자열 형태의 데이터만 저장이 가능하기 때문이다. 이를 통해 브라우저를 닫았다가 열어도 저장된 리뷰 데이터가 유지된다.

따라서 이 함수는 reviews 배열을 로컬 스토리지에 REVIEWS_LS 키로 저장한다. 이후에 이 데이터를 가져와서 다시 배열로 변환하여 사용할 수 있다.


4. 리뷰 표시 함수

위에서 submit 후 값을 출력하기 위해 paintReview함수를 틀만 제작해두었었다. 이 함수를 마저 작성해보도록 하겠다.

//리뷰 표시 함수
function paintReview(text, userId, password) {
  const li = document.createElement("li");
  const delBtn = document.createElement("button");
  const updateBtn = document.createElement("button");
  const span = document.createElement("span");
  const timeSpan = document.createElement("span"); 
  const newId = reviews.length;
  //span은 리뷰의 텍스트를 담을 요소, newID는 새로운 리뷰를 배열에 추가할 때 사용할 새로운 id
  const currentTime = new Date(); // 현재 시간을 얻음

위 코드에 대해 설명하기 전, 별 거 아니지만 필자가 헷갈려했던 부분.

Q. handleSubmmit에서 사용한 paintReview 함수 내의 변수명과 현재 변수명이 다르지 않나?

-> handleSubmmit에선 위에 정의해 두었던 변수들을 가져와 쓰는 것이기 때문에 함수를 호출하여 변수명을 사용하는 것은 문제가 없다.

= 호출 시에 변수 명은 함수 내에서 선언된 매개변수명과 일치할 필요가 없음



이어서 설명을 해보자면, 필자가 원하는 출력되는 리뷰 내용은

  1. 입력했던 리뷰
  2. 리뷰 작성 시간
  3. 리뷰 수정버튼
  4. 리뷰 삭제버튼
    이렇게 4가지다.

새로운 리뷰들을 담을 < li > 요소를 생성
삭제버튼, 수정버튼 생성
리뷰의 텍스트를 담을 < span > 요소를 생성
리뷰 작성 시간을 표시할 < span > 요소를 생성해주고,

새로운 리뷰의 id를 생성해준다. 여기서는 reviews의 길이를 이용하여 새로운 id를 할당하는데, 새로운 리뷰가 배열에 추가될 때 마다 새로운 id를 부여하기 위함이다.

그리고 마지막으로, 현재 시간을 가져오는 Date객체를 생성해준다.

Q. span 요소를 생성하는 이유?

텍스트를 그룹화 하고 리뷰 텍스트를 span요소에 넣어주기 위함이다.
< li >요소는 목록의 항목을 나타내는데에 사용되므로 그 내부에 직접 텍스트를 넣기보다는 < span > 태그를 이용하여 텍스트를 담는 것이 구조상 더 적절하다.


이 코드를 설명하기 전에, 작성된 리뷰에는 리뷰 삭제, 리뷰 수정 버튼이 들어가게 되는데, 그 동작을 수행해줄 함수를 정의해줘야한다.

function updateReview() {
}
function deleteReview(){
}

이렇게 현재 작성 중인 함수 외부에 정의해주고 마저 작성해보겠다.

  delBtn.innerText = "리뷰 삭제";
  updateBtn.innerText = "리뷰 수정";
  delBtn.addEventListener("click", deleteReview);
  updateBtn.addEventListener("click", updateReview);
  span.innerText = text;
  timeSpan.innerText = currentTime.toLocaleString(); // 시간을 문자열로 변환하여 출력

버튼 내 텍스트 넣어주고, 아까 정의해 준 함수들에 이벤트 리스너를 추가해준다. 클릭 이벤트가 발생하면, 위 함수가 수행된다.

매개변수로 받은 text의 값을 span 요소에 설정해준다.

시간을 문자열로 변환, 현재 로컬 시간을 사용해준다.


  li.appendChild(span);
  li.appendChild(timeSpan); // 시간 추가
  li.appendChild(delBtn);
  li.appendChild(updateBtn);
  //li요소에 각각 요소를 추가하여 화면에 표시
  li.id = newId;
  //새로 생성된 리뷰의 id 할당
  reviewList.appendChild(li);
  //새로 생성된 리뷰를 화면에 추가

새로운 리뷰의 텍스트 내용을 담는 < span > 요소를 li 요소의 자식으로 추가
새로운 리뷰의 작성 시간을 나타내는 < span > 요소를 li 요소의 자식으로 추가
리뷰 삭제를 위한 버튼 요소를 li 요소의 자식으로 추가
리뷰 수정을 위한 버튼 요소를 li 요소의 자식으로 추가
새로 생성된 리뷰의 ID를 할당해준다. 이 ID는 리뷰를 식별하는 데 사용된다.
새로 생성된 li 요소를 리뷰 목록을 나타내는 reviewList에 추가


  const reviewObj = {
    text,
    userId,
    password,
    time: currentTime.getTime() // 현재 시간을 밀리초로 변환하여 저장
  };
  //새로운 리뷰에 대한 객체 생성

  reviews.push(reviewObj);
  saveReviews();
}

//리뷰 객체를 배열에 추가
//새로운 리뷰가 추가된 배열을 로컬 스토리지에 저장
// => 새로운 리뷰가 입력되면 화면에 표시되고, 해당 정보가 배열에 추가. 화면을 새로고침해도 유지.

reviewObj에 새로운 리뷰를 저장해준다. 객체로 묶어서 관리함으로서 효율적인 관리가 가능하며 이 객체를 배열에 추가함으로서 여러개의 리뷰를 순차적으로 관리할 수 있다.

이 객체를 reviews배열에 추가한다.
배열의 내용이 추가로 인해 변경되었으므로 saveReviews() 함수를 호출하여 변경된 리뷰 목록을 로컬 스토리지에 저장해준다.


//리뷰 삭제 함수
function deleteReview(event) {
  const btn = event.target;
  const li = btn.parentNode;
  //이벤트가 발생한 요소와 그 부모요소인 li찾기
  const password = prompt("비밀번호를 입력하세요:");

  if (password !== null && password === reviews[li.id].password) {
    reviewList.removeChild(li);
    //일치 여부 확인 후 리뷰 목록에서 해당 리뷰 삭제
    reviews.splice(li.id, 1);
    //배열에서 해당 리뷰 삭제
    saveReviews();
    //삭제된 리뷰를 로컬 스토리지에 저장
  } else {
    alert("비밀번호가 일치하지 않습니다.");
  }
}

//리뷰 수정 함수(업데이트)
function updateReview(event) {
  const btn = event.target;
  const li = btn.parentNode;
  const password = prompt("비밀번호를 입력하세요:");

  if (password !== null && password === reviews[li.id].password) {
    const newText = prompt("수정할 리뷰를 입력하세요:", reviews[li.id].text);
    if (newText !== null && newText !== "") {
      reviews[li.id].text = newText;
      //배열에 저장된 해당 리븅의 텍스트를 새로운 텍스트로 업데이트
      li.querySelector("span").innerText = newText;
      //화면에 해당 리뷰의 텍스트를 새로운 텍스트로 업데이트
      saveReviews();
      //업데이트된 리뷰를 로컬 스토리지에 저장
    } else {
      alert("리뷰를 입력하세요.");
    }
  } else {
    alert("비밀번호가 일치하지 않습니다.");
  }
}





//로컬 스토리지에서 리뷰 데이터 가져오기
function loadReviews() {
  const loadedReviews = localStorage.getItem(REVIEWS_LS);

  if (loadedReviews !== null) {
    const parsedReviews = JSON.parse(loadedReviews);
    //가져온 리뷰 데이터를 json 형식에서 js객체로 변환
    parsedReviews.forEach(function (review) {
      paintReview(review.text, review.userId, review.password);
    });
  }
}
//변환 된 리뷰 데이터를 순회하며 각 리뷰를 화면에 표시

//초기화 함수
function init() {
  loadReviews();
  //웹페이지가 로드될 때 저장된 리뷰를 로드, 화면에 표시
  reviewForm.addEventListener("submit", handleSubmit);
}

init();
//페이지 초기화. 

남은 부분은 2편으로 가져오겠다.

profile
프론트엔드 개발자를 향해서

0개의 댓글