본문 바로가기

TIL

[TIL-128] 위코드 29일차: React JS - 댓글 달기 / JS 코드카타

ReactJS

 

 

...(spread 연산자)로 깊은 복사

개념

배열이나 객체는 참조형 데이터이다. 그래서 배열 a를 변수 b에 할당해주면, a와 b는 특정 메모리 주소를 가진 같은 배열을 참조하게 된다. 따라서 a의 요소를 바꾸면 a가 참조하고 있는 배열이 변경되므로, 해당 배열을 마찬가지로 참조하고 있는 b도 바뀐 배열을 반환한다. 참조형 데이터일 때 이렇게 같은 주소값을 할당하는 것은 얕은 복사이다.

깊은 복사는 공통으로 참조하는 주소값이 아니라 새로운 주소값을 할당하여 독립적인 배열/객체를 생성한다. 그런데 spread 연산자를 쓰면 최상위 단계의 요소에서만 깊은 복사로, 두 단계 이상 깊어지면 얕은 복사가 된다.

 

사용법

function Article() {
  const [inputValue, setInputValue] = useState();
  const [comments, setComments] = useState([]);
  const input = useRef();

  const handleComments = event => {
    setInputValue(event.target.value);
  };

  const addComment = event => {
    event.preventDefault();
    setComments([...comments, inputValue]);
    input.current.value = '';
  };
  
  ...
  
}

 

 

참고

JS - 깊은복사와 얕은복사 그리고 spread operator에 대해서 https://velog.io/@euneun/JS-%EA%B9%8A%EC%9D%80%EB%B3%B5%EC%82%AC%EC%99%80-%EC%96%95%EC%9D%80%EB%B3%B5%EC%82%AC-%EA%B7%B8%EB%A6%AC%EA%B3%A0-spread-operator%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C

 

 

 

배열형state.map()

사용법

function Article() {
  const [inputValue, setInputValue] = useState();
  const [comments, setComments] = useState([]);

  const handleComments = event => {
    setInputValue(event.target.value);
  };

  const addComment = event => {
    event.preventDefault();
    setComments([...comments, inputValue]);
  };


  return (
    <CommentList
      comments={comments}
      username="hae_nim"
    />
  );
};

function CommentList(props) {
  return (
    <div className="feedColumnRow comments">
      {props.comments.map(comment => (
        <Comment username={props.username} text={comment} />
      ))}
    </div>
  );
};

function Comment(props) {
  return (
    <p>
      <span className="boldText">{props.username}</span> {props.text}
    </p>
  );
};

최상위 부모 컴포넌트(Article)에서 생성한 배열 형태의 state(comments)를 자식 컴포넌트(CommentList)에 props로 보내주었다. 그리고 CommentList 컴포넌트에서 props로 받아온 이 배열 state(comments)의 요소들을 map() 메서드를 사용해서 <Comment> 컴포넌트를 그리는 데 이용했다.

 

key 속성

Warning: Each child in a list should have a unique "key" prop. Check the render method of `Comments`. See https://reactjs.org/link/warning-keys for more information.

map() 메서드를 사용하여 여러 컴포넌트 요소를 반환할 때, UI를 그려내는 데는 성공했지만 콘솔에 이러한 Warning이 떴다. 내용은 각각의 자식(컴포넌트)이 고유의 "key" prop을 가져야 한다는 것이다.

참고) https://www.geeksforgeeks.org/reactjs-lists/

function Comments(props) {
  return (
    <div className="feedColumnRow comments">
      {props.commentsData.map(comment => (
        <Comment
          key={comment.id}
          username={comment.userName}
          text={comment.content}
        />
      ))}
      {props.newComments.map((comment, index) => (
        <Comment key={index} username={props.currentUsername} text={comment} />
      ))}
    </div>
  );
}

map() 메서드로 JSX 요소들의 리스트를 만들 때, 리스트의 각 항목에 key를 넣어야 한다. key는 생성된 각 요소들에 할당될 특수한 문자열 속성(attribute)이다. props와 같은 형태로 컴포넌트에 입력하지만, 일반적으로 우리가 사용하는 그런 props는 아니라서 console에 찍어보면 key라는 prop은 나오지 않는다.

key는 JSX 요소 생성 후, 리스트에서 어떤 항목을 변경할지 식별하기 위해 필요하다. 데이터의 id와 같은 고유 식별 문자열을 사용하는 것이 좋다. 반환되는 제일 바깥 태그에 부여한다.

key가 필요한 이유는 하나 더 있다. 리액트의 중요한 특징은 virtual dom을 이용하여 렌더링할 때 변경된 부분만 그려낸다는 점이다. 그런데 map() 메서드를 통해 만들어낸 요소들의 리스트에 key가 없다면, 어떤 요소가 새로 추가되었거나 삭제되었는지 확인할 수 없다. 그럼 처음부터 코드 하나하나 모두 일일이 확인해서 변경된 부분들을 판별할 것이므로 비효율적이다. key가 dom 트리 안에서 map()이 반환한 리스트를 요소 단위로 비교할 때 기준이 되는 것 같다.

<ul>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

<ul>
  <li key="2014">Connecticut</li>
  <li key="2015">Juke</li>
  <li key="2016">Villanova</li>
</ul>

또한 위 예시에서 key가 있다면, 2014번이 추가된 것을 쉽게 인지하는 것뿐 아니라, key가 2015인 <li> 태그의 내용이 변경된 것도 쉽게 확인할 수 있다. key가 없었다면 내용이 "Duke"였던 기존의 <li> 태그가 변경된 것인지, "Duke"가 삭제되고 "Juke"가 새로 생겼는지 알 수 없다.

key의 이러한 사용 목적 때문에, index를 key로 사용하면 배열이 재배열될 경우 key가 바뀔 수 있어서 권장하지 않는다.

 

참고

리스트와 Key   https://ko.reactjs.org/docs/lists-and-keys.html

자식에 대한 재귀적 처리 https://ko.reactjs.org/docs/reconciliation.html#recursing-on-children

 

 


 

코드 카타

 

로마자 문자열을 인자로 받아 숫자로 계산하기

내 답안

function romanToNum(s) {


  const romanNums = {I: 1, V: 5, X: 10, L: 50, C: 100, D: 500, M: 1000};
  let result = 0;

  for (let i = 0; i < s.length; i++) {
    let foreNum = romanNums[s[i]];
    let backNum = romanNums[s[i+1]];
    if(i === s.length-1) {
      result += foreNum;
      break;
    }
    if(foreNum >= backNum) {
      result += foreNum;
    } else {
      result -= foreNum;
    }
  }
  
  return result;
 }