JSX에서 list를 랜더링 하는 방식
도깨비젤리
·2022. 4. 3. 23:59
한 줄 요약
🦄React는 Array와 같이 이더너블 한 객체 안에 있는 태그들을 인덱스 순서대로 차례차례 랜더링한다🦄
🦄이때, Virtual DOM에서 어떤 요소에 변화가 있는지 감지하기 위해 부여하는 안정적인 고유값이다🦄
빌드업
리액트를 사용하다보면, 동일한 타입을 가진 데이터들을 비슷한 모습의 컴포넌트로 랜더링 해야할 경우가 많다.
여러개의 컴포넌트를 렌더링 하기 위해서 리액트는 엘리먼트의 모음을 { } 사이로 낑겨넣어서 JSX에 포함시킬 수도록 해줍니다.
쇼핑몰 홈페이지에 보여질 품목 리스트를 화면에 그린다고 간주해봅시다.
const items = [
{title:'노트북',price:'1000000',stock:12},
{title:'커피콩', price:'5000', stock:100},
{title:'포켓몬빵',price:'1200', stock:0}]
보여줘야할 아이템들이 모두 동일하게 title, price, stock 필드를 가지고 있으므로, 각 멤버에 직접 접근하여 태그를 만드는 것보다는, javascript 에서 제공하는 map() 함수를 사용하여 노동소요를 줄이는 것이 더 현명해보입니다.
const items = [
{title:'노트북',price:'1000000',stock:12},
{title:'커피콩', price:'5000', stock:100},
{title:'포켓몬빵',price:'1200', stock:0}]
// 순회할수 있는 객체의 엘리먼트를 랜더링할 때는 아래와 같이 map 함수를 쓰는게 일반적이다.
function ArrayWithMap(){
const listItems = items.map((elem, index)=>(
<ul key={index}>
<li>{`상품명 : ${elem.title}`}</li>
<li>{`가격 : ${elem.price}`} </li>
// map 함수 내에서 삼항 연산자등을 사용하여 조건에 따라 다른 화면을 그릴 수 있다.
{elem.stock > 0 ? <li>구매 가능</li> : <li>구매 불가</li>}
</ul>
))
return(
<div>
{listItems}
</div>
)
}
// 이건 말도 안된다!
// 만약 items의 길이를 코드상에서 알 수 없다면, 이런 노가다를 하고 싶어도 하지 못하게 된다
function ArrayWithIndex(){
return(
<div>
<ul>
<li>{`상품명 : ${items[0].title}`}</li>
<li>{`가격 : ${items[0].price}`}</li>
<li>{items[0].stock > 0 ? '구매 가능' : '구매 불가'}</li>
</ul>
<ul>
<li>{`상품명 : ${items[1].title}`}</li>
<li>{`가격 : ${items[1].price}`}</li>
<li>{items[1].stock > 0 ? '구매 가능' : '구매 불가'}</li>
</ul>
<ul>
<li>{`상품명 : ${items[2].title}`}</li>
<li>{`가격 : ${items[2].price}`}</li>
<li>{items[2].stock > 0 ? '구매 가능' : '구매 불가'}</li>
</ul>
</div>
)
}
결과적으로 ArrayWithMap의 listItems는 랜더링 시점에서 아래와 같은 꼴이 됩니다.
[
(<ul key=0>
<li>상품명 : 노트북</li>
<li>가격 : 1000000</li>
<li>구매 가능</li>
</ul>),
(<ul key=1>
<li>상품명 : 커피콩</li>
<li>가격 : 5000</li>
<li>구매 가능</li>
</ul>),
(<ul key=2>
<li>상품명 : 포켓몬빵</li>
<li>가격 : 1200</li>
<li>구매 불가</li>
</ul>)]
이런 꼴의 배열을 { } 중괄호 안에 넣으면 React는 자동으로 배열을 순회하면서 각 요소들을 화면에 그려줍니다.
그런데, 이렇게 만들어지는 엘리먼트들은, map 함수로 인해서 너무나도 유사한 형태를 가지고 있기 때문에, key라는 요소로 엘리먼트에 안정적인 고유성을 부여해줘야합니다.
key 자체를 부여해주지 않아도 화면에 랜더는 되나, 개발자 도구를 켜보면 각 엘리먼트에 unique 한 키 값을 부여해줘야한다고 경고창이 뜹니다. 언젠가 이 경고를 끝까지 무시하면 어떻게 될까 해서 악으로 깡으로 계속 기능을 구현 해보았는데, 체크박스 리스트를 key 없는 맵으로 구현하니, 한 항목을 체크하자 다른 항목들도 모두 체크가 되고, 체크를 풀자 특정 한 요소만 체크가 남아있는 등, 이상 동작을 하는 것이 기억납니다.
여하튼, map으로 생성하는 엘리먼트의 가장 최외곽 (부모) 컴포넌트에는 key 값을 꼭 넣어줍시다.
본문
위에서 설명했던 내용은, 리액트를 배우면 필수적으로 거쳐가는 너무나도 당연한 얘기입니다. Array를 JSX.Element로 바꾸려면 map 함수를 이용하고, unique 한 key 값을 최외곽에 부여하라는 말 말입니다.
그런데, map만을 계속 사용하다보니, 리액트의 동작 방식이 머리속에서 희미해지고, 그저 좀비처럼 "Array는 map을 사용한다." 라는 행동만 반복하게 되었습니다.
그러나, 최근에 React가 Array를 랜더링 하는 방법을 십분 활용하여 문제를 깔끔하게 해결한 일이 있어 공유합니다.
문제 : 데모 계정이 특정 Route에 접근하지 못하도록 구현한 코드가 너무 길고 난잡하다.
// 유저의 상태를 나타내는 bucketStatus가 DEMO면, 특정 페이지로 연결되는 Route를 랜더하지 않는다
{bucketStatus === 'DEMO' ? ('') : (
<>
<ProtectedRoute
path="/setting-company"
component={CompanyInfoManagementPage}
exact
/>}
{bucketStatus === 'DEMO' ? ('') : (
<>
<ProtectedRoute
path="/setting-contracts"
component={SettingContractsPage}
exact
/>
</>
)}
일부만 짤라와서 별로 안 추해보일 수도 있지만, 실제 코드는 이런 경우가 무지막지하게 많았습니다. 덕분에, 코드도 굉장히 wet하고 추잡해보여서, 이를 끝장내기로 하였습니다.
해결 : Array 를 이용하여 삼항연산자의 최소화
{bucketStatus === 'DEMO' ? ('') : (
[
// needs unique key (react jsx att)
<ProtectedRoute
key="setting-company"
path="/setting-company"
component={CompanyInfoManagementPage}
exact
/>,
<ProtectedRoute
key="setting-contracts"
path="/setting-contracts"
component={SettingContractsPage}
exact
/>]
)}
ProtectedRoute는 React-Route-DOM v5의 요소를 활용하여 만든 커스텀 엘리먼트로, 로그인 여부에 따라 Redirect 혹은 Route 컴포넌트를 랜더링 합니다. 즉, 화면 요소가 없는 컴포넌트이기 때문에, 배열 안에 있는 요소들의 순서를 크게 따질 필요가 없습니다. 그저 조건에 따라 랜더 할 것인지 말것인지가 가장 중요한 팩터이고, 조건에 따라 몇개의 요소들이 랜더링 되어야하는지도 정해져 있기 때문에 (기능적인 부분이므로) 위와 같이 구현하는것이 깔끔해보입니다.
직접 키를 적어넣으며 Array를 코딩한다는게 좀 묘한 기분이였지만, 그래도 리액트 본연의 동작 방식을 활용했다는 설레임이 같이 밀려옵니다.
'웹 > React' 카테고리의 다른 글
[Next.js] Data Fetching의 방법들 (1) | 2022.09.20 |
---|---|
[React 기초] Todo list 만들기 :: 3)InputForm,TODOS (0) | 2021.06.12 |
[React 기초] Todo list 만들기 :: 2) App, Container, Titlebar (0) | 2021.06.12 |
[React 기초] Todo list 만들기 :: 1) Intro (0) | 2021.06.12 |