[JS] 새로 고침 시 좋아요 상태 유지

Yunhye Park·2023년 12월 13일
0

상황

  • 이미 구현 완료 : 로그인 한 유저가 메인 페이지의 여러 코멘트 중 하나 이상의 하트 아이콘 클릭 시 (FE) 아이콘이 빨간색으로 바뀌고 (BE) Comment_like DB에 해당 레코드가 생성된다. 재클릭 시, (FE) 아이콘이 원래대로 돌아오고 (BE) 해당 레코드가 삭제된다.

  • 추가할 부분 : 새로고침 시에도 해당 아이콘 상태 유지

로직

  1. FE
    데이터(commentid) 담아서 axios 요청

  2. BE
    useridx와 commentid 기준으로 DB에 동일한 레코드 존재하는지 찾고 해당 데이터 전달

  3. FE
    페이지의 데이터와 전달 받은 데이터가 동일하다면 해당 아이콘 색 변경(class 추가)

전체 코드

// 컨트롤러

exports.post_main = async (req, res) => {
  let valueArr = [];

  for (const key in req.query) {
    if (req.query.hasOwnProperty(key)) {
      const value = req.query[key];
      valueArr.push(Number(value));
    }
  }

  const likedCmt = 
        useridx ? await getLikedComments(useridx, valueArr) : [];

  res.send({ likedCmt: likedCmt });
};

// 유저가 좋아요 한 코멘트 찾는 함수
async function getLikedComments(useridx, valueArr) {
  try {
    const likedComments = await Comment_like.findAll({
      attributes: ['commentid'],
      where: {
        useridx: useridx,
        commentid: {
          [Op.in]: valueArr,
        },
      },
    });

    return likedComments.map((comment) => comment.commentid);
  } catch (err) {
    console.log('새로고침 err:', err);
    return [];
  }
}
// main.ejs

// 반복문 내부
<i class="fa-solid fa-heart heart-icon"
data-commentid="<%=data.sec2[i].commentid%>"
onclick="clickLike(this, '<%=data.sec2[i].commentid%>')">
  </i>


// 스크립트
document.addEventListener('DOMContentLoaded', async () => {
  // 새로 고침 시 좋아요 유지
  let commentids = [];
  const heartIcon = document.querySelectorAll('.heart-icon');

  heartIcon.forEach(element => {
    const commentid = element.getAttribute('data-commentid');
    commentids.push(Number(commentid));
  });

  axios({
    method: 'post',
    url: '/',
    params: commentids,
  }).then(res=>{
    const likedCmt = res.data.likedCmt;

    heartIcon.forEach(element=> { 
      const dataCommentId = element.getAttribute('data-commentid');

      if (likedCmt.includes(Number(dataCommentId))) {
        element.classList.add('heart-active');
      }
    })
  });
        
  // 이후엔 다른 기능이어서 이벤트 닫는 괄호 생략

1. 데이터 담아서 axios 요청

1.1. 동적 UI를 위한 데이터 전송 : POST 요청

처음엔 axios get 요청할 생각이었다. 데이터 생성이 아닌 조회하는 대목이기 때문에 RESTful을 고려해서다. 페이지 렌더하는 컨트롤러가 이미 존재해서 이 데이터를 함께 전달했는데 프론트에서 받아올 수가 없었다.

왜냐,렌더링은 페이지를 보여주는 게 목적이다. 비동기 처리처럼 동적 UIres.send로 데이터를 보내줘야 하고, POST 요청이 적절했다.

1.2. id는 고유하다

반복문에 있는 요소를 모두 가져올 땐 그 요소를 id가 아닌 class로 지정해야 한다. id는 고유한 하나에 지정하는 거라서 여러 개를 id로 가져오면 첫번째로 일치하는 1개만 담긴다.

ex.

const commentIds = document.querySelectorAll('.commentids');

1.3. 사용자 정의 태그 : data-

백으로 보낼 데이터는 모든 코멘트의 id인데, 이건 i에 담겨있지 않다. 이럴때 사용자정의태그를 이용하여 특정 데이터를 담아둘 수 있다. data-접두어 뒤에 원하는 대로 작명하고 getAttribute로 받아온다.

  let commentids = [];
  const heartIcon = document.querySelectorAll('.heart-icon');

  heartIcon.forEach(element => {
    const commentid = element.getAttribute('data-commentid');
    commentids.push(+commentid);
  });

querySelectorAll로 받아온 데이터는 nodelist라는 유사배열 타입이다. 따라서, 배열 메서드는 활용할 수 없기에 forEach문으로 각 요소를 순회했다.

그리고 이렇게 가져온 데이터(commentid)는 string이기 때문에 타입을 바꿔주는 작업(+)도 거친다.

2. DB 데이터와 비교 후 전달

2.1. 필드에서 특정 값 검색 : [Op.in]

sequelize 모듈 Op.in은 SQL의 IN과 비슷한 속성으로, 주어진 배열의 요소와 일치하는 데이터 값이 포함된 레코드를 반환한다.

const likedComments = await Comment_like.findAll({
  attributes: ['commentid'],
  where: {
    useridx: useridx,
    commentid: {
      [Op.in]: valueArr,
    },
  },
});

여러 값을 가진 필드에서 특정 값을 검색할 때 유용하다.

3. 해당 아이콘 색 변경

3.1. 함수(메서드)는 함수 스코프가 있다

백에서 제대로 데이터를 보냈는데 클라이언트 측에선 undefined가 뜬다면, 앞서 말한 것처럼 1) 비동기 데이터를 렌더링할 때 받아오는 로직으로 잘못 작성했거나 2) 데이터 처리를 외부에서 했는지 확인해 볼 필요가 있다.

백에서 받아온 데이터는 then 메서드 내부에 담긴다. 메서드도 함수라서 내부의 스코프는 별개로 존재한다. 따라서, 메서드 안에서 모든 동작 코드를 작성해 주었다.

형변환(+dataCommentId)도 잊지 말자!

  axios({
    method: 'post',
    url: '/',
    params: commentids,
  }).then(res=> {
    const likedCmt = res.data.likedCmt;

    heartIcon.forEach(element=> { 
      const dataCommentId = element.getAttribute('data-commentid');

      if (likedCmt.includes(+dataCommentId)) {
        element.classList.add('heart-active');
      }
    })
  });

소감

  • node+express에 ejs 템플릿 환경에서 새로고침 시 좋아요 기능 구현한 포스팅을 못 봤는데 나와 비슷한 처지(?)에 놓인 사람들에게 도움이 되었으면 좋겠다.

  • 구현이 잘 안 될 때엔 한글로 먼저 할 일을 순서대로 정리하고, 하나씩 해나가다 보면 된다. 무엇보다 시간을 두고 끝까지 물어지는 게 중요한 듯하다.

  • 백과 프론트가 서로 데이터를 주고받는 방식에 대해서 좀 더 이해한 느낌이다. 프로젝트 들어가기 전만 해도 각자의 역할이 종종 헷갈렸는데 그간 많이 성장한 것 같아 뿌듯하다.

profile
일단 해보는 편

0개의 댓글