배민이나 쿠팡이나 어떠한 사이트든 댓글을 입력하는 부분이 존재한다.
이쁜 댓글 화면
어떤 게시판 글에 댓글을 구현하기 위해선
댓글에 해당되는 ui와 Container
를 해당 게시글에서 구현을 해도 되지만, 그렇게 구현 한다면 Container / Presenter 디자인에 조금 위배되지 않나 싶다.
하나의 화면에 하나의 기능을 담당해야 할 친구가 하나의 화면에 두개의 기능을 담고 있다면 가독성부분이나 나중에 댓글부분을 수정하기 위해서는 스크롤을 위 아래로 계속 해야할 경우가 생길것이다.
따라서 댓글부분의 Container와 Presenter를 또 따로 구현 후에 Presenter부분만 게시물의 아래부분에 보여주게 한다면 될것이다.
게시물을 보여주는 BoardInfo
와 댓글부분을 담당하는 BoardComment.
s아직 댓글부분을 담당하는 BoardComment
는 TypeScript
로 변환하지 않았다.
BoardInfo.presenter
부분의 상단 부분에
import BoardComment from '../../../../src/components/board/boardcomment/BoardComment.container'
를 통해 import 후, 하단 댓글 부분에 불러와준다.
자 그럼 일차적으로 Presenter
부분과 Container
부분으로 나눈 댓글 기능을 구현하는 화면을 게시물 하단에 그려주도록 import
시켯으니 presenter
와 container
를 보도록 하자
당연하게도 만일 해당 게시글의 id
로 Graphql
로 query
를 날려 댓글이 있다면 불러 와야하고, 입력한 댓글을 mutation
시켜야 하니 해당 graphql
을 가져오자
import {useQuery,useMutation} from '@apollo/client';
또한 댓글 입력은 동적으로 값이 입력되고 그것들을 객체에 한데 모아 보내야 하니 useState
와 담아줄 그릇을 선언해 주자.
import { useState } from 'react'
그런데 아이디값을 어떻게 불러와야할까? 이전에 구현한 것들을 보면 이미 가져온 id
값을 동적라우트 시켜 라우트의 주소로 들어가 있으니 이를 이용한다면 매우 편하게 쓸 수 있을것이다. 따라서 라우트 기능도 가져오자
import{useRouter} from "next/router";
마지막으로 미리 정의해 둔 presenter의 내용과, 정의한 query문들을 가져오면 일차적인 준비는 끝난다.
const router = useRouter();
const postLoadComment = BOARD_COMMENT;
const postComment = BOARD_COMMENT_POST;
const [inputs, setInputs] = useState({
boardId: router.query.id,
writer:'',
password: '',
contents: '',
rating: Number(0),
})
담아줄 그릇 즉 inputs에는 일단 미리 현재 가져온 id값을 설정 해주고, 나머지 부분들도 알맞게 넣어준다.
여기서 rating
이란 키값은 다음에 작성할 별표 할당에서 더 자세히 설명하겠다.
const {data, loading, error} = useQuery(postLoadComment,{
variables: {boardId : String(router.query.id)}
})
일단 해당 글에 존재하는 댓글을 불러오는 쿼리문을 날려주기위해 data, loading, error를 디스트럭처링을 통해 선언 한 후 variables
에 현재 게시물의 아이디를 담아준다.
자 이제 댓글을 불러오는 쿼리는 문제가 없을 것이니, Presenter
부분에서 조금 만져주면 된다
data는 props에 있으니 이를 디스트럭처링으로 가져 오고 그 댓글의 갯수 만큼 그려주면 된다.
data.fetchBoardComments
를 map을 사용하여 존재하는 데이터의 갯수 만큼 그려준다. for문은 왠만하면 지양하도록 하자.
코드안의 div
로 묶은 부분은 바로 댓글에 해당하는 별의 사진이 rating
이라는 숫자에 해당되는 수 만큼 노랑별을 찍고, 그 수를 넘은 부분은 회색별로 찍어 주는 부분이다.
처음에 배열을 하나 생성하고, 배열내부를 undefined
로 채워주기 위해 fill()라는 메소드를 사용한 후, map을 통해 index를 사용한다.
쉽게 설명하자면 미리 5개의 별이 들어갈 공간을 할당하고, index와 해당 댓글의 별점을 비교하고, 만일 별점보다 현재 index가 작다면 노랑별, 그렇지 않다면 회색별을 찍어준다.
별점이 4점이라 가정하자. 그렇다면 첫번째 index는 현재 1일것이니 노랑별을 하나 그려준다.
그리고 index가 1 증가한다. 또다시 별점과 비교한다. 그래도 작기 때문에 노랑별을 그려준다.
그렇게 나아가다가 만일 현재 index가 5가 된다면 별점 4보다 높으니 회색별을 그려준다.
이렇게 댓글을 불러오는 부분은 쉽게 구현 했으니 이제 댓글을 적어보자.
const handleOnChange = (e) =>{
setInputs({
...inputs,
rating: Number(starRating),
[e.target.id] : e.target.value
})
console.log(inputs);
}
이전에 썻던 게시물 입력부분과 똑같다. index
를 spread
연산자로 복사 한 후, setInputs
을 통해 그 값을 복사한다. 그런데 rating
부분은 인자를 숫자만 받기 때문에 rating
은 Number
로 형변환 시켜서 넣어주자.
하지만 여기서 드는 의문점이 있다.
댓글을 쓰고 나서 쓴 댓글을 확인 하려면 그 페이지가 다시 새로고침 되어야 하지 않는가?
그러기 위해 Graphql은 RefetchQueries를 지원하는데.
이는 하나의 mutation이 종료 하면 다시 특정 query문을 날려주는 매우 파워풀한 기능이다.
async function hadleClickPostComment(){
const postCommentAction = await postCommentRequest({
variables:{...inputs},
refetchQueries: [{ query: BOARD_COMMENT,
variables: {boardId : String(router.query.id)}}],
});
console.log('test Comment data');
}
mutation을 실행하는 부분이다. 여기서 추가된 부분은 바로
refetchQueries: [{ query: BOARD_COMMENT,
variables: {boardId : String(router.query.id)}}],
});
부분인데, refetchQueries[{query: 재실행 할 쿼리}, variables: 쿼리가 실행되기 위해 필요한 매개변수]
이다.
즉 우리는 위에서 PostCommentRequest
가 를 실행 후, 다시 refetchQueries
를 이용해 이전에 페이지가 로딩될때 요청했던, 댓글을 불러오는 쿼리를 한번 더 실행한다.
잘 작동한다
presenter
안에서 1~5까지의 배열을 미리 선언 한 후, 이를 반복하며 아이디와 클릭 이벤트를 구현하고
누른 별의 id, 즉 만일 눌렀을때의 id를 기준으로 별을 그려준다.
const [starRating, setStarRating] = useState(0)
별점의 상태를 저장하는 state도 미리 만들어 준다. 누를때 마다 새로 rating에 그 아이디를 저장 해 주자.