본문 바로가기
> 개발/React

[React] map()을 이용한 댓글 기능 구현

by @일리 2023. 2. 24.

2022년 12월 27일 작성

 

위스타그램 메인 화면에서 댓글을 다는 기능을 구현했다! 기능을 구현하기 전에 내가 입력한 값을 comment에 담고, 그것들을 commentList 배열에 담아서 map()을 이용해야겠다는 계획을 세웠지만 map() 을 어떻게 써야할지가 너무 막막했다. 일단 접근 방법은 아니까 이틀정도 시간이 날 때마다 틈틈이 구현에 도전했지만 구현할 수가 없었다.

 

어떻게 해야 할까 고민하다가 다짐을 했다. 하나는 문제가 해결되지 않았을 때 기존의 위코드 수강생들의 코드를 찾아보지 않는 것이다. 그러기 위해서 map() 으로 어떻게 리스트를 뿌려주는지 영상이나 글을 찾아보고, 내 코드에 적용하는 식으로 공부했다. 구현이 성공했을 땐 기존 수강생들의 코드를 보고 내가 한 방법 말고 다른 구현 방법은 없을지 고민해보았다.

 

또 다른 다짐은 큰 것부터 해결하려하지 않고 작은 것부터 차근차근 도전하는 것이다. 이 기능의 경우 1. 미리 만들어둔 commentList 를 map()으로 전개해보고 2. 내가 입력한 comment 값을 commentList로 만들어서 map()으로 전개하고 3. 댓글 컴포넌트를 만드는 식으로 단계별로 진행했다.

1. 고정된 commentList : map() 적용

const commentList = [
    { user: 'love_penguin', content: '감기 조심해!!' }, 
    { user: 'love_penguin2', content: '오늘 진짜 너무 춥다 ㅠㅠ' },
    { user: 'love_penguin3', content: '내일이 월요일이라니 끔찍해' },
  ];


{commentList.map(item => {
              return (
                <div className="comment">
                  <span className="userName">{item.user}</span>
                  <span className="comment_content">{item.content}</span>
                </div>
              );
            })}

map() 을 어떻게 사용해야 할지가 막막해서 commentList 목록을 직접 만들고, 이 목록을 map()을 이용해 화면에 보이도록 하는 코드를 먼저 작성했다. 정말 신기했던 것은 map()에 JSX 를 입력해서 HTML 태그와 className, 변수를 모두 전달할 수 있었다는 점이다.

 

또 하나 새로웠던 점은 map()을 사용할 때 return 을 써야한다는 것이다. 나는 map()으로 nums.map(el => el*2) 정도 써보아서 return을 써야하는 상황이 있는 줄 몰랐다..화살표 함수는 암묵적으로 return 이 된다고 기억하고 있었기 때문이었다. 다행히 vscode에서 문제점으로 return 을 쓰라고 알려줘서 빨리 문제를 해결할 수 있었다! 따봉 vscode야 고마워!! 함수 공부 더 열심히 해볼게!!

2. 입력된 값으로 commentList 만들기

[Feeds 컴포넌트 js 부분]
function Feeds() {
  const [commentList, setCommentList] = useState([]);
  const [content, setContent] = useState('');

  // comment = input 값
  const saveComment = e => {
    setContent(e.target.value);
  };

const pushCommentList = () => {
    setCommentList([
     ...commentList,
      {
        id: commentList.length + 1,
        user: 'love_penguin',
        content: content,
      },
    ]);
  };
[Feeds 컴포넌트 return 부분]
            {commentList.map((el, i) => {
              return (
                <div className="comment">
                  <span key={el.id} className="userName">
                    {el.user}
                  </span>
                  <span key={i} className="comment_content">
                    {el.content}
                  </span>
                </div>
              );
            })}
          </div>
          <div className="feeds_comment_register_section">
            <input
              type="text"
              className="comment_input"
              placeholder="댓글 달기..."
              onChange={saveComment}
            />
            <button className="comment_btn_register" onClick={pushCommentList}>
              게시
            </button>
            ....

이 단계에선 input에 입력한 값을 comment에 넣어주고, commentList 에 id와 user, content가 담긴 객체를 추가해주고, commentList에 담긴 comment를 map()으로 전개해주었다.

 

이 코드는 오류가 많은 코드라서 절대절대 가져다 쓰면 안된다. 오류 없는 코드를 만들고 난 뒤에 다시 내가 작성했던 코드들을 보니 내가 얼마나 고군분투했는지가 보여서 재밌다.

 

이때 index를 key로 주면 안된다는 걸 몰라서 index를 key로 줬다가 나중에 id(배열의 length+1)를 key로 고치다 만 모습도 보이고, key를 어디에 줘야하는지 몰라 아무 곳에 입력해 둔 게 눈에 띈다🤣🤣🤣

 

아! 기능을 구현할 때 댓글이 추가가 되지 않고 등록된 댓글의 내용이 인풋값으로 계속 바뀌는 문제가 있었다. 이것은 [...commentList] 스프레드 연산자로 해결이 되었다. 스프레드 연산자를 사용하지 않으면, commentList가 {id : x, user : 'love_penguin', content : '댓글내용'} 이런 length가 1인 commentList로 바뀌기만 할 뿐, 새로운 값이 추가가 되지 않는다. 마침 스프레드 연산자를 혼자 공부한 날이였는데 바로 적용해 기분이 정말정말 좋았다 ><

3. 댓글 컴포넌트화하기

[부모 요소]
{commentList.map(comment => {
              return (
                <div key={comment.id} className="comment">
                  <Comments user={comment.user} content={comment.content} />
                </div>
              );
            })}

[자식 요소 : Comments]
const Comments = props => {
  const { user, content } = props;

  return (
    <>
      <span className="userName">{user}</span>
      <span className="comment_content">{content}</span>
    </>
  );
};

export default Comments;

짠~~ Comments 컴포넌트를 따로 만들었고 props 를 이용해 값을 전달해주었다. props를 그냥 이용하는 방법과 구조분해할당으로 변수를 만들어주는 방법이 여러가지가 있는데 이것은 다음에 쓸 글에 작성하도록 하겠다!!

 

댓글 컴포넌트화하는 과정에서도 문제가 있었다. 위에 써있는 코드로 수정하기 전까지 댓글은 정상적으로 작동하나 콘솔창에는 key 관련 에러메세지가 떴다. 알고 보니 map() 에 입력한 key가 문제였다.

 

map()에서 key는 고유한 식별자 역할을 한다. 식별자 역할이 필요한 이유는 어떤 요소에 변화가 생겼는지를 파악해 변경된 부분만 효율적으로 다시 렌더링을 하기 위함이다.

 

오류가 발생했을 때 내가 자식 컴포넌트의 span 쪽에 key 값을 줬었는데 부모 컴포넌트에 div 태그(span의 부모 요소) 에 key 값을 주니 문제가 해결되었다. span에 key값을 각각 부여하면 그것은 고유한 key값이 아니니 문제가 되었던 것 같다. 나의 경우에는 반복될 요소가 span 태그 두개였는데, 만약 반복될 요소가 <li> 하나라면 <li key = {comment.id}>{comment.content}</li> 처럼 li 태그에 바로 key를 줘도 된당!!

 

참고로 key 값으로 index를 주는 것은 권장하지 않는다. 댓글의 경우 추가 외에도 삭제가 될 수 있는데 이렇게 순서가 변동되면 Index 값이 변경되기 때문이다. 하지만 무조건 index를 key값으로 사용하면 안돼! 는 아니고 상황에 따라 적절하게 사용하면 된다고 한다.

알면 알수록 더 멀어지는 것 같은 map()...계속 쓰다 보면 언젠가 가까워지는 날이 올거라고 믿는다 ㅠ_ㅠ

댓글