본문 바로가기
Udemy - React완벽가이드

랜더링 리스트 및 조건부 Content

by Hanachoi 2023. 2. 28.

# 데이터의 랜더링 목록

  • 현재까지 우리가 만든 프로그램은 하드코딩이 되어있는 상태로, dynamic 하게 데이터를 받아오지 못하고 있다. form에 새로운 정보를 입력해도 별다른 업데이트가 일어나지 않는다.
  • 현재 <ExpenseItem>컴포넌트에 직접적으로 하드코딩을 해두었지만, 이렇게 하는건 지양해야 한다. 
  • <Expense>컴포넌트에서 App.js에 있는 expenses 배열을 랜더링하고 싶다. 
  • 첫번째 단계는 props를 통해 expenses를 전달해주는 것이다.  item prop으로 넘겨주고 있고, item은 {expenses} 배열을 가리키고 있다. 하지만 현재 이용하고 있지 않았다. 이 부분을 바꿔보자

App.js 파일에서 <Expenses>컴포넌트에 items라는 props를 통해서 expenses 배열을 전달 하는 중

  • 우리는 지금 <expenses> 컴포넌트에서 각각의 <ExpenseItem>들을 랜더링 하고 싶다. 
  • <ExpenseItem>컴포넌트들 위에 JSX 신텍스를 사용해 배열의 기본 내장 메서드인 map()을 사용해준다.
  • map()은 다른 배열을 기반으로 새로운 배열을 생성하는데, 원본 배열에 있는  모든 요소들을 변환한다.
  • https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/map
 

Array.prototype.map() - JavaScript | MDN

map() 메서드는 배열 내의 모든 요소 각각에 대하여 주어진 함수를 호출한 결과를 모아 새로운 배열을 반환합니다.

developer.mozilla.org

  • 우리의 expenses 배열을 변환하기 위해서 여기서 map을 사용해줄 수 있다. map은 인자로 함수를 받아주는데, 함수의 결과값은  새로 생성될 배열에 추가될 요소다.
  • expenses의 배열을 JSX요소의 배열로, 정확히 말하면 JSX요소인 전체 <ExpenseItem의> 배열로 변환해줄 수 있다.
  •  예를들어 {[<Card /> , <Card />  ]} 이렇게 <Card /> 컴포넌트가 두개 들어있는 배열에 map을 돌려주면 리액트는 간단하게 <Card /> 컴포넌트들을 나란히 랜더링 해줄 수 있다.
  • map은 함수를 받아주는데, 그 함수가 모든 요소를 실행해서 현재 매개변수로 실행되고 있는 요소를 얻을 수 있다. 
  • 여기서는 expense를 매개변수로 받아와 JSX 요소인 <ExpenseItem> 컴포넌트에 매핑을 해준다.  expense 오브젝트를 특별한 객체, JSX 요소로 변환해준다. 

  • 여기서 <ExpenseItem> 컴포넌트도 다시 props를 받아줄 수 있다. 
  • expense 는 자동적으로 함수를 매개변수로 전달하는데 이게 map의 작동방식이기 때문이다. 그리고 expense는 title을 추출하는데 사용된다.

  • 같은 원리로 title, amount, date를 모두 받아올 수 있다.

  • 그리고 밑에 하드코딩 되어있던 코드를 삭제해줄 수 있게 된다.
  • 이렇게 만들어도 페이지는 그대로 작동을 하는데, 이건 배열에 기반을 두고 있기 때문에 동적으로 움직인다고 할 수 있다. 

  • 배열을 변경하면 배열의 변경사항이 여기에 바로 반영된다. 

 

#state 저장목록 사용

  • expense 배열에 새로운게 추가될때 마다 어떻게 업데이트를 할 수 있을까?
  • App.js 파일로 이동해줘야 한다. expenses 배열이 거기에 저장되어 있기 때문, 그리고 addExpenseHandler 함수도 가지고 있다. 이함수는 새로운 expense가 추가될 때 마다 작동한다. 
  • addExpenseHandler 함수는 expense를 매개변수로 받아오는데, 이걸 받아서 expenses 배열에 넣어주게 만들 것이다. 하지만 이상태에서는 상태가 업데이트 되지 않기 때문에, state를 사용해줘야 한다. 
  • expenses 배열을 function App() 밖으로 빼주고 DUMMY_EXPENSES라는 이름으로 바꿔준다. 이 데이터는 그냥 연습용 데이터이기 떄문. 그리고 useState를 사용해서 App컴포넌트 함수에 전달해준다. 

App 컴포넌트 밖으로 DUMMY_EXPENSES를 뺴준다.

  • 그리고 App 컴포넌트에 useState를 넣어준다. 
  • DUMMY_EXPENSES를 초기인자로 받아오는 useState를 만들어준다. 

  • 이제 상태를 변경해주는 useState를 이용해 addExpenseHandler를 바꿔준다. 
  • setExpenses를 넣어주는데, 얘는 배열을 가질 수 있다. 
  • 배열안에는 새롭게 추가되는 expense를 넣어주고, 그 다음 스프레드 연산자로 기존에 이미 들어있던 expenses를 받아온다.

  • 스프레드 연산자는 객체 뿐만 아니라 배열에도 사용 가능! 이건 기존 자바스크립트의 기능이다.
  • 하지만! 이렇게 코드를 작성하면 적절하지 못하다.
  • 이전 상태의 스냅샷에 의존해서 상태를 업데이트 한다면 이 상태의 업데이트르 위해 특별한 함수 폼을 사용해야 한다.
  •  setExpeenses 함수 안에 매개변수로 또 다른 함수를 전달해준다. 그 함수는 자동적으로 최신 상태의 스냅샷을 받게 해준다. 
  • prevExpenses를 매개변수로 받고 또 다른 배열을 return 해주는데,  새롭게 들어오는 값인 expense를 넣어주고 기존에 있던 배열을 스프레드 연산자와 prevExpenses를 통해서 이전 데이터를 받아온다. 이게 우리의 상태를 업데이트 할 수 있는 더 깔끔한 방법이다. 

  • 그리고 이제 <Expense />컴포넌트에서 expenses 를 사용해주고 있기 때문에 아이템이 추가되었을 때 자동적으로 추가되는 동적인 목록을 가져야한다. 
setExpenses를 설정하는 방법 두 가지 모두 작동하는데 왜 두번째 방식을 쓰라고 하는걸까?
- 두가지를 모두 실행해보면 똑같이 잘 작동함. 근데 영상에선 두번째처럼 함수를 넣어서 사용하라고 함
- 이유를 찾아봤음
- 결론적으로 두가지 모두 작동하긴 하고 사용가능함. 하지만 첫 번째 방식은 나중에 코드가 복잡해 질 경우 에러가 발생할 가능성이 높아진다. 
- 첫번째 방식은 저 함수 실행 컨텍스트에 저장된 상태를 사용하는 형태이고, 두번째 방식은 상태를 업데이트 하기 바로 전에 리액트에서 가장 최신상태를 불러오는 방식이다.
- 두번째 방식에서 prev를 꼭 붙여줘야 하는건 아니다. 상위에 있는 상태변수와 이름만 구분되면 됨

 

 

# "Keys" 이해하기

  • console 창을 열어보면 key 경고 메세지가 계속 뜨는 걸 볼 수 있다.
  • 리액트는 데이터의 목록을 랜더링 하는데 있어서 특별한 개념을 가진다. 리액트가 발생할 수 있는 어떤 성능 손실이나 버그없이 효과적으로 그런 목록들을 업데이트 하고 랜더링할 수 있도록 보장하기 위해 존재한다. 
  • 지금 우리가 form에 새로운 정보를 입력하면 아래의 목록에 새로운 지출목록들이 업데이트 되도록 만들었다. 목록의 최 상위에 새로운 지출아이템들이 들어가는 것이다. 

새로운 아이템을 입력하니 목록 최상단에 추가가 되었다.

  • 하지만 이 아이템이 추가될때 개발자 도구창에서 Element들을 보면, 가장 아래에 있는 <div>창이 반짝이는걸 볼 수 있다. 이렇게 반짝인다는 뜻은 새로운 Element가 추가되거나 지워졌을때 발생하는 현상이다.
  • 그렇다면 우리의 인터페이스와는 다르게 작동한다는걸 알 수 있다. 원래는 가장 위에있는 <div>가 번쩍여야하지만 가장 아래의 <div>로 추가가 되기 때문이다.

아이템이 추가될 떄 가장 아래의 <div>로 아이템이 추가된다.

  • 하지만 막상 마지막<div>를 클릭해보면, 원래 우리의 인터페이스처럼 그대로 목록에 가장 아래에 있는 아이템인 New Dask(Wooden)이 들어가 있는걸 볼 수 있다.

가장 마지막 아이템인 New Desk로 <div>가 잘 들어가져있다.

  • 이런 현상이 발생하는 이유는, 리액트는 새롭게 들어오는 아이템을 이 <div>목록에 있는 마지막 아이템으로 랜더링하고 모든 아이템을 업데이트해서 컨텐츠를 교체하기 때문이다. 이렇게 작동되는게 좋은 방법은 아니다. 리액트가 보기에는 비슷한 <div>목록들이 들어온거고 이 <div>들이 들어있는 배열만 길어진 것을 보았기 때문에 벌어진 일이다. 그래서 마지막에 그냥 추가한뒤 나머지를 일치시키도록 업데이트 한 것이다. 이렇게 되면 버그발생 가능성이 높아진다.
  • 리액트는 이렇게 비슷해보이는 아이템들 속에서 랜더링 된 아이템들만 확인하기 때문에 새로운 아이템이 어느 위치에 추가되어야 하는지 모르는 상황이다.
  • 그래서 우리는 새로운 아이템이 어디에 추가되어야 하는지 리액트에게 알려줘야 한다. 이때 사용하는게 key props이다.
  • 새로운 지출목록이 들어오는 <ExpenseItem> 컴포넌트에 key를 추가해준다. 모든 expenses들은 고유의 id 값을 가지고 있기때문에 불러주면 된다. 

  • 만약에 고유 id가 없다면, map() 함수의 두번째 인자로 index를 불러줄 수 있다. map에 전달하는 함수에서 자동으로 얻어지는 것으로 이 함수는 자동으로 인덱스를 관리해준다.  하지만 이것도 완벽한 방법은 아니다. 여전히 버그를 발생시킬 수 있고 특정한 아이템에 대한 인덱스가 항상 똑같기 때문이고 아이템 컨텐츠에 직접적으로 첨부된 것이 아니기 때문이다. 
  • 특정 컨텐츠를 갖는 모든 아이템들은 분명하게 고유한 id값을 가지고 있어야 한다. 어떤 원시형 타입이나 고유식별자로 사용할 수 있다. 
  • 이렇게 아이디값을 넣어주면 리액트는 모든 아이템들을 식별할 수 있게 되고, 다시 개발자도구에서 Element창을 봤을때, 새로운 아이템이 배열 가장 상단 <div>로 들어가지는걸 알 수 있다. 
  • 이렇게 되면 리액트는 배열의 길이 뿐만 아니라 아이템이 위치해야 할 곳까지 인식해준다. 
  • 목록을 매핑할때는 항상 KEY를 추가해줘야 한다!
  •  

'Udemy - React완벽가이드' 카테고리의 다른 글

차트 추가하기  (0) 2023.03.09
연도 filter 작동시키기  (0) 2023.03.03
Form 제출 처리하기  (0) 2023.02.20
양식 입력 추가하기  (0) 2023.02.19
useState 훅 자세히 살펴보기  (0) 2023.02.18

댓글