bluegram - 13일차

박상은·2021년 12월 25일
0

🍃 blegram

목록 보기
17/20

1. webpack 설정 추가

1.1 svg

기존에는 svg에 사용할 모든 아이콘을 넣고 background-position을 이용해서 아이콘을 보여주는 형식으로 사용했습니다.
하지만 사용하면서 불편함을 많이 느낀 게 background-position도 일일이 찾아서 잡아줘야 하고, 사이즈 조절도 불편했으며, 색상 변경도 할 수 없었습니다.

이런 불편함을 겪고 남들은 어떻게 사용하나 조금 찾아보니 @svgr/wepack로더를 사용하면 svg파일을 컴포넌트처럼 사용할 수 있는 것을 알게 되었습니다.
( 확인해보니 index.html<style>로 들어가는 걸로 확인했습니다. )

애초에 하나의 파일에 여러 개의 이미지를 넣고 사용했던 이유가 이미지 파일을 여러 개 불러오면 요청이 성능상 안 좋다고 알고 있어서 그런 건데 컴포넌트로 분리해서 사용한다면 이미지처럼 불러와서 사용하는 것이 아니라서 나눠서 만들어서 성능상으로 문제가 없다고 생각해서 만들었던 모든 아이콘을 각각의 svg파일로 나눠서 다시 제작했습니다.

그리고 props로 여러 값들을 받아서 형태, 크기, hover, animation, clickEvent 등을 등록해서 독립적인 역할을 가지도록 만들었습니다.

  • Iconprops
    1. shape: 정해진 이름을 전달받으면 해당 아이콘을 그려줌 ( 기본 값: avatar )
    2. width, height: 아이콘의 크기 결정 ( 기본 값: 24px )
    3. fill: 어떤 색으로 채울지 결정 ( 기본 값: black )
    4. onClick: 이벤트 등록
    5. hoverfill: hover 시 채울 색 결정
    6. animation: 정해진 animation 이름을 주면 해당 애니메이션 실행

  • /component/common/Icon/index.jsx의 Proptypes

Icon.propTypes = {
  shape: Proptypes.string,
  width: Proptypes.number,
  height: Proptypes.number,
  fill: Proptypes.string,
  onClick: Proptypes.func,
  hoverfill: Proptypes.string,
  animation: Proptypes.string,
};

1.2 HtmlWebpackPlugin

어느정도 기본은 만들었다고 생각해서 테스트로 빌드해보고 확인해보니 HtmlWebpackPlugin에서 만들어주는 index.html파일에 <div id="root"></div>가 없어서 오류가 나는걸 확인했습니다.

문제를 해결하기 위해서 구글링해서 얻은 결과가 template이라는 속성에 기반이 될 html파일을 넣어주면 그 html파일을 기반으로 새로운 html파일을 생성하는 것을 알게 되어서 추가로 index.html을 만들고 해당 파일을 넣어줬습니다.

그리고 이왕 넣어주는 김에 <meta>도 이것저것 추가해서 간단하게 만들었습니다.
이후에 배포하기전에 자세히 공부하고 추가할 예정입니다.

// svg 파일을 위한 로더 설정 추가
{
  test: /\.svg$/,
  use: ["@svgr/webpack"],
}
  
// template 추가
new HtmlWebpackPlugin({ title: "bluegram", template: "./index.html" })

2. assets

위에서 말한 아이콘을 모두 별도의 파일로 분리해서 assets라는 폴더를 만들고 내부 icon폴더에 모두 넣고 사용중입니다.
/src/assets

3. 아이콘 클릭 시 모달창 닫힘 문제

특정 게시글 모달창에서 아이콘을 클릭하면 다른 아이콘으로 바뀌도록( 좋아요 아이콘을 누르면 내부가 채워진 좋아요 아이콘으로 변경 )되도록 만들었습니다.

하지만 이상하게도 아이콘을 클릭하니 모달창이 닫히는 문제가 발생해서 원인을 찾아보니 아이콘이 변경되고 이벤트가 호출되는 순서상의 문제가 있다는 걸 알았습니다.

현재 모달창이 닫히는 원리는 windowclick 이벤트를 달고 모달창 내부를 클릭 시 이벤트 버블링을 통해서 window에서 이벤트를 받아서 모달창에 속해있는 노드라면 모달창이 닫히지 않고 그 반대라면 모달창이 닫히도록 구현했습니다.

하지만 모달창에서 아이콘을 클릭하면 즉시 아이콘이 다른 아이콘으로 변경되고 그 이후에 window에서 이벤트를 받아서 내부 노드들에 속해있는지 검사하면 클릭했던 아이콘은 현재 모달창에 속해있지 않으므로 모달창이 닫히게 되는 문제가 생긴것 입니다.

약간 꼼수를 이용해서 문제를 해결했습니다.
icon은 항상 <path><svg> 내부에 존재해서 노드의 이름을 이용해서 닫히지 않도록 구현했습니다.

  // 2021/12/21 - 다른 영역 클릭 시 모달 닫기 이벤트 - by 1-blue
  const handleCloseModal = useCallback(
    e => {
      /**
       * 바로 아랫부분 추가한 이유는 게시글 모달창 내부에서 좋아요 같은 아이콘을 누르게 되면 즉시 다른 아이콘으로 변경해 줌
       * ex) heart 아이콘 ==> fillHeart 아이콘
       * 이때 문제가 발생하는 게 누르는 즉시 아이콘을 변경시키므로 이벤트 버블링이 돼서 widdow에서 click 이벤트를 받기 전에
       * 아이콘이 변경되어버림... 그래서 모달창 내부에 있는 태그임에도 불구하고 누르면 영역 외의 태그라고 판단해서 모달창이 닫히기 때문에
       * 아이콘은 특별처리로 눌러도 모달창이 닫히지 않도록 해주는 코드임
       */
      if (e.target.nodeName === "path" || e.target.nodeName === "svg") return;

      if (showModal && !modalRef.current?.contains(e.target)) {
        setShowModal(false);
        dispatch(resetPostAction());
      }
    },
    [showModal],
  );

4. sequelize

4.1 belongsToMany의 include 사용

belongsToMany관계 즉, N : M관계에서 include를 사용할 경우에는 관계 테이블에 특정 작업을 하고 싶을 때는 through를 사용하면 됩니다.

아래 코드는 UserPostbelongsToMany관계를 맺고 있고, 중간 테이블은 likes, User 별칭은 Liked, Post 별칭은 Likers로 지정한 상태입니다.
through를 사용하지 않으면 중간 테이블의 모든 컬럼을 가지고 오게 되는데 중간 테이블을 조정하려면 아래처럼 through를 사용하면 됩니다.

const posts = await Post.findAll({
  {
    model: User,
    as: "Likers",
    attributes: ["_id"],
    through: {
      attributes: ["updatedAt"],
    },
  }
});

4.2 belongsToMany의 메서드들

많은 메서드들이 생기는 걸로 알고 있지만 실제로 사용한 메서드들만 정리하겠습니다.

아래 코드는 UserPostbelongsToMany관계를 맺고 있고, 중간 테이블은 likes, User 별칭은 Liked, Post 별칭은 Likers로 지정한 상태입니다.

  • 생성된 메서드에 넣을 값은 상식적으로 생각했을 때 게시글 객체의 메서드를 사용하는 거니까 게시글 관련 데이터는 필요 없고 관련된 유저의 데이터 중에서 중간 테이블에 필요한 값 즉, 유저의 식별자 값만 있으면 되니까 유저의 식별자를 넣어준다고 이해하면 될 것 같습니다.
const targetPost = await Post.findOne({ where: { _id: PostId } });

// 보유여부
await targetPost.hasLikers(UserId);		// true or false

// 생성
const [result] = await targetPost.addLikers(UserId);	// 중간 테이블(likes)에 생성한 데이터 배열로 반환

// 삭제
await targetPost.removeLikers(UserId);		// 삭제된 유저의 아이디 반환

4.3 belongsToMany의 foreignKey 지정

좋아요 기능을 구현하고 나서 보니 자꾸 좋아요가 반대로 생성되고 삭제되는 현상이 발생해서 원인을 찾다보니 기존에 사용했던 squelizelike관련 모델 생성 코드에서 문제가 발견되었습니다.
좋아요는 N : M관계라서 중간 테이블을 likes로 생성했고, Workbanch에서 확인해 보니 UserIdforeignKeyposts._id를 가리키고 PostIdforeignKeyusers._id를 가리키는 서로 반대로 참조하는 상황이 발생했습니다.

그래서 코드를 아래로 수정했습니다.

// 기존 코드
db.Post.belongsToMany(db.User, { through: "likes", as: "Likers", foreignKey: "UserId", onDelete: "cascade" });
db.User.belongsToMany(db.Post, { through: "likes", as: "Liked", foreignKey: "PostId", onDelete: "cascade" });

// 수정 코드
db.Post.belongsToMany(db.User, { through: "likes", as: "Likers", foreignKey: "PostId", onDelete: "cascade" });
db.User.belongsToMany(db.Post, { through: "likes", as: "Liked", foreignKey: "UserId", onDelete: "cascade" });

5. 좋아요 기능 구현

  • POST /like/post/:PostId: 게시글에 좋아요 추가 요청
  • DELETE /like/post/:PostId: 게시글에 좋아요 추가 요청
  • 404: 존재하지 않는 게시글에 요청을 보낼 경우 응답 코드
  • 409: 이미 눌렀거나 누르지 않은 상태에 다시 요청을 보낼 경우 응답 코드
// 2021/12/25 - 좋아요 추가 - by 1-blue
router.post("/post/:PostId", isLoggedIn, async (req, res, next) => {
  const PostId = +req.params.PostId;
  const { _id: UserId } = req.user;

  try {
    const targetPost = await Post.findOne({ where: { _id: PostId } });

    // 2021/12/25 - 존재 하지 않는 게시글에 좋아요 누른 경우 - by 1-blue
    if (!targetPost) {
      return res
        .status(404)
        .json({ message: "존재하지 않는 게시글에 좋아요를 누르셨습니다.\n새로 고침 후 다시 시도해 주세요" });
    }

    // 2021/12/25 - 좋아요를 누른 게시글에 다시 좋아요 추가 요청인 경우 - by 1-blue
    if (await targetPost.hasLikers(UserId)) {
      return res.status(409).json({ message: "이미 좋아요를 누른 게시글입니다.\n새로 고침 후 다시 시도해 주세요." });
    }

    // 2021/12/25 - 정상적으로 좋아요 추가 - by 1-blue
    const [result] = await targetPost.addLikers(UserId);

    res.json({ message: `${req.user.name}님 게시글에 좋아요를 누르셨습니다.`, result });
  } catch (error) {
    console.error("POST /like/:PostId >> ", error);
    next(error);
  }
});

마무리

sequelize를 어느 정도 공부하고 프로젝트에 적용한다고 생각했는데 기본 세팅부터 잘못한 걸 보고 아직 부족하다는 것을 느꼈습니다.
내일은 개발보다는 sequelize에 대한 이론 공부에 집중해 볼 생각입니다.

0개의 댓글