리액트를 다루는 기술 : 컴포넌트 반복

김명성·2022년 3월 20일
0
'리액트를 다루는 기술'을 보고, 공부하고 정리한 내용을 리마인드 하기 위해 작성하고 있습니다.
제가 사용하는 예시는 책에서 나온 예시와는 다릅니다.
그동안 배웠던 내용들을 조금씩 더하였기에 예시에 부정확한 부분이 있을 수 있습니다.
댓글로 피드백을 주신다면 감사한 마음으로 정독하겠습니다.

컴포넌트 반복


웹 앱을 만들다보면, 반복되는 코드를 작성할 때가 있다
<ul>
<li>2022년 1월 개봉영화 : batman</li>
<li>2022년 2월 개봉영화 : superman</li>
<li>2022년 3월 개봉영화 : spiderman</li>
<li>2022년 4월 개봉영화 : pacman</li>
</ul>
이런식으로 직접 수기로 작성하다보면, 유지보수도 어렵고 파일 용량도 증가할 것이다. 또한 입력되야할 데이터가 유동적이라면 관리할 수 없게 될것이다.
리액트 프로젝트에서 반복적인 내용을 효율적으로 보여 주고 관리하는 방법을 알아보자.

Javascript Array Function : map()

map 함수를 사용하여, 동일 내용이 반복적으로 들어가는 html tag와 text를 줄일 수 있다.
map 함수 첫번째 매개변수로 callback function을 받으며, callback function의 결과를
새로운 배열로 리턴한다.
callbackfunction은 3개의 매개변수를 받는데, 그 중 1,2번째가 많이 쓰인다.
  1. currentValue : 배열 내 요소
  2. index : 요소의 배열번호
const ArrayMap = () => {
   const moviesArray = ["batman","superman",'spiderman',"pacman"];
 const moviesList = {moviesArray.map((movie,idx) => <li>2022{idx+1}월 개봉영화 : {movie}</li>)}
   return (
       <div>
       {moviesList}
       </div>
   );
};
export default ArrayMap;
// output
// 2022년 1월 개봉영화 : batman
// 2022년 2월 개봉영화 : superman
// 2022년 3월 개봉영화 : spiderman
// 2022년 4월 개봉영화 : pacman
결과는 성공적으로 나오지만 개발자 도구의 콘솔창을 보면 각 childprop마다 고유한 keyprop을 가지고 있어야 한다는 오류메세지가 출력된다.
map
위 주제와는 관계 없지만 map함수는 forEach 반복문과 유사하다.
하지만 map 함수는 기존의 배열을 가지고 새로운 배열을 만들어 return한다.
또한 기존의 배열을 변형시키지도 않으므로 불변성을 침해하지 않는다.

key

리액트에서 key는 컴포넌트 배열을 랜더링했을 때 어떤 원소에 변동이 있었는지 알아내려고 사용한다. 유동적인 데이터를 다룰 때에는 key prop을 통해 리액트에게 변동되고 있다는 사실을 알려주어야 한다.

key 설정

key 값을 설정할 때에는 map 함수의 인자로 전달되는 함수 내부에서 컴포넌트 props를 설정하듯이 설정해주면 된다.
key 값은 항상 유일해야 한다. 물론 map 함수의 callback function에 들어가는 index도 key 값이 될 수 있지만 차선책이며 숫자만 들어가는 것 보다 더 복잡한 키값을 설정할 수 있다면 그것으로 사용하는 것이 좋다. index를 key로 설정하면 배열이 변경될 때 효율적으로 리랜더링을 하지 못하기 때문이다.
const moviesList = {moviesArray.map((movie,idx) => 
  <li key={idx}>2022{idx+1}월 개봉영화 : {movie}</li>)}

동적인 배열 랜더링 구현

위의 예시는 정적이고, 단순한 문자열로 이루어진 배열로 구현하였지만, 이번에는 id번호와 영화명으로 이루어진 객체로 이루어진 배열을 만들고, 사용자가 직접 영화명을 입력하면 출력될 수 있게 만들어보자.
  const [moviesTitle,setMovieTitle] = useState([
      {id: 1, title: 'batman'},
      {id: 2, title: 'superman'},
      {id: 3, title: 'spiderman'},
      {id: 4, title: 'pacman'},
  ]) 
  
  const moviesList = moviesTitle.map((movie,idx)=> 
		<li key={movie.id}>2022{idx+1}월 개봉영화 : {movie.title}</li>);
    
	return (
        <div>
        {moviesList}
        </div>
    );
key 값을 idx가 아닌 movie.id로 주었다.

새로운 영화 등록하기.

만든 영화타이틀 상단에 text를 입력할 수 있는 인풋, submit할 수있는 button을 만들어보자. div안에 생성되는 list를 감쌓을 ul도 넣어주자.
그리고 input에 입력되는 값을 받아 id,title로 저장할 state를 만들어주자

    const inputRef:React.RefObject<HTMLInputElement> = createRef();
  const [moviesTitle,setMoviesTitle] = useState([
      {id: 1, title: 'batman'},
      {id: 2, title: 'superman'},
      {id: 3, title: 'spiderman'},
      {id: 4, title: 'pacman'},
  ]) 
  const [newMovie,setNewMovie] = useState({
      id: moviesTitle.length,
      title: ''
  })

  const handleNewMoviePush = () =>{
      setMoviesTitle([
          ...moviesTitle,
          newMovie
      ])
      setNewMovie({
          id:moviesTitle.length,
          title: ''})
          inputRef.current?.focus();
  }
  const handleTitleChange = (e:ChangeEvent<HTMLInputElement>) =>{
    setNewMovie({
        id: moviesTitle.length +1,
        title:e.target.value
    })
    console.log(newMovie);
    console.log(newMovie.title);
    
  }
  const moviesList = moviesTitle.map((movie,idx)=>
		<li key={movie.id}>2022{idx+1}월 개봉영화 : {movie.title}</li>);
                                     
                                     
    return (
           <div>
      <input
        ref={inputRef}
        value={newMovie.title}
        onChange={handleTitleChange}
      />
      <button onClick={handleNewMoviePush}>추가하기</button>
      <ul>{moviesList}</ul>
    </div>
    );
};
newMovie state는 객체로 id값으로 movieTitle의 length값을 갖는다. title의 초기값은 빈 문자열이다.
handleTitleChange는 input tag에 있는 이밴트 함수로 event 대상인 input의 value를 받고, length +1하여 NewMovie에 저장된다.
handleNewMoviePush는 button tag에 있는 이밴트 함수로 moviesTitle에 newMovie의 값을 넣은 뒤 value를 초기화 해 준다. 이때 input에도 newMovie.title을 value로 갖게하여 서로 마주보게 만들어야 한다. (input value와 e.target.value는 보통 이렇게 마주보게 만들면 상호작용이 높아진다.)
지난시간에 배운 ref로 input에 focus를 주는 방법을 적용해 영화를 입력하면 다시 input으로 포커싱이 되게 만들어보았다.

데이터 제거 기능 구현하기

각 항목을 더블클릭했을 때 해당 항목이 화면에서 사라지는 기능을 구현해보자. state는 불변성을 유지하면서 업데이트해 주어야 한다. 불변성을 유지하면서 배열의 특정 항목을 지울 때에는 배열의 내장 함수 filter를 이용한다.
filter의 사용 방법
  const [moviesTitle, setMoviesTitle] = useState([
    { id: 1, title: 'batman' },
    { id: 2, title: 'superman' },
    { id: 3, title: 'spiderman' },
    { id: 4, title: 'pacman' },
  ]);

const iHatePacMan = moviesTitle.filter(movie => !movie.title.includes('pac'))
전체 문자열이 아닌 특정 문자열만 입력하여도 적용됨을 볼 수 있다.
filter의 사용 방법2
const number = [1,2,3,4,5,6];
const withoutThree = number.filter(number => number !== 3); // [1,2,4,5,6]
HTML 요소를 더블 클릭하면 filter 함수가 적용되는 이밴트를 만들어보자.
이밴트의 대상은 map함수로 반복 생성되는 li tag다.
 const onRemove = (id:number) => {
    const removeTitle = moviesTitle.filter(movie => movie.id !== id) 
    setMoviesTitle(removeTitle)
  }
  const moviesList = moviesTitle.map((movie, idx) => (
    <li onDoubleClick={()=>onRemove(movie.id)} key={movie.id}>
      2022{idx + 1}월 개봉영화 : {movie.title}
    </li>
  ));
1. li tag에 onDoubleClick 이밴트를 달아준다.
2. anonymous arrow function을 작성하고 함수 블록에 onRemove function을 호출하고 매개변수로 movie.id를 넣어준다.
arrow function 안에 onRemove function을 담은 이유 : 클로저의 원리를 사용하는 고차함수
onDoubleClick 이밴트가 발생되면, onRemove 함수를 즉시 실행하는게 아니라 Arrow function의 함수 블록에 onRemove 함수를 담아주었다.
Arrow function에 onRemove를 담지 않으면 onRemove(movie.id)가 되고
이것은 onRemove() 형태로 즉시 함수를 실행하는 실행문이 되어버린다.
여기서는 onDoubleClick 이밴트가 발생되었을 때 onRemove함수를 실행해야 하기에, () =>{} 형식으로 작성하여 함수를 보관하는 것이다.
li tag에서 () => {onRemove(movie.id)} 형태로 작성을 원치 않는다면, onRemove 함수 표현식에서 사용할 수 있다.
const onRemove = (id:number) => 
    () => {
    const removeTitle = moviesTitle.filter(movie => movie.id !== id) 
    setMoviesTitle(removeTitle)
    }
  
  const moviesList = moviesTitle.map((movie, idx) => (
    <li onDoubleClick={onRemove(movie.id)} key={movie.id}>
      2022{idx + 1}월 개봉영화 : {movie.title}
    </li>
  ));
onRemove 함수는 익명함수를 return하는 고차함수다.

0개의 댓글