반응형
조건부 렌더링(conditional rendering)
- React에서는 상황에 따라 동일한 컴포넌트이지만 서로 다른 내용을 화면에 표시해야 할 경우가 생길 수 있다. 이러한 경우 다음과 같은 방법들을 사용하여 조건부로 JSX 코드를 렌더링할 수 있다.
- if / else 조건문
- 삼항 연산자(? :)
- 논리 AND 연산자(&&)
- React에서 조건부 렌더링은 자바스크립트의 조건문 처리와 비슷하게 동작한다. if 문이나 조건부 연산자와 같은 자바스크립트 연산자를 사용하여 현재 상태를 나타내는 엘리먼트를 생성하면, React는 현재 상태에 맞도록 UI를 업데이트할 것이다.
if / else 조건문
// LoginPanel.js
// 사용자의 로그인 상태에 따라 Login 버튼과 Logout 버튼의 렌더링 여부를 결정
const Button = ({ name, isLoggedIn }) => {
if (isLoggedIn) {
return null;
}
return <button>{name}</button>;
};
const LoginPanel = () => {
return (
<>
<Button isLoggedIn={false} name="Login" />
<Button isLoggedIn={true} name="Logout" />
</>
);
};
export default LoginPanel;
삼항 연산자(? :)
- 삼항 연산자(ternary operator)는 유일하게 피연산자를 세 개나 가지는 조건 연산자로, 다음과 같은 문법을 통해 사용할 수 있다.
- 물음표(?) 앞의 표현식의 결과에 따라 결괏값이 true이면 반환값1을 반환하고, 결괏값이 false이면 반환값2를 반환한다.
condition ? exprIfTrue : exprIfFalse
// LoginPanel.js
const Button = ({ name, isLoggedIn }) => {
return isLoggedIn ? null : <button>{name}</button>;
};
논리 AND 연산자(&&)
- 또 다른 방법으로 자바스크립트의 논리 AND 연산자(&&)를 사용하여 조건부 렌더링을 표현할 수도 있다.
- 논리 AND 연산자는 주로 React 컴포넌트 내부에서 조건이 true일 때는 일부 JSX를 렌더링하지만 false일 경우에는 아무것도 렌더링하지 않을 때 자주 사용된다.
// App.js
const Button = ({ name, isLoggedIn }) => {
return !isLoggedIn && <button>{name}</button>;
};
배열 렌더링
map()을 활용한 배열 렌더링
- React 앱을 구현하다 보면 데이터에서 여러 개의 유사한 컴포넌트들을 가져와 표시해야 할 경우가 종종 발생한다. 이 때 자바스크립트 배열 객체의 map() 메소드를 사용하면 반복되는 코드를 간결하게 구현할 수 있다.
- map() 메소드는 인수로 전달된 콜백 함수(callback function)를 배열의 각 요소에 대해 한 번씩 순서대로 호출한 후, 그 반환값들을 모아 새로운 배열로 생성하여 반환한다.
arr.map(callback(currentValue[, index[, array]])[, thisArg])
- map() 메소드의 콜백 함수는 호출될 때 처리할 현재 요소의 값(currentValue), 처리할 현재 요소의 인덱스(index), 그리고 map() 메소드를 호출한 원본 배열(array)까지 총 3개의 인수를 전달 받는다.
- map() 메소드는 호출한 원본 배열의 값을 변형하지는 않지만, 콜백 함수에 의해서 변형될 수는 있다.
// List.js
// 배열의 각 요소를 리스트 아이템으로 렌더링
const bucketItems = [
"세계 일주 여행",
"스카이 다이빙",
"오로라 구경하기",
"마라톤 완주"
];
const List = () => {
// 1. bucketItems 배열의 각 요소에 대해 map() 메소드를 호출하여 각각의 <li>엘리먼트를 생성
// 2. 생성된 엘리먼트들을 새로운 JSX 엘리먼트 리스트인 bucketList에 저장
const bucketList = bucketItems.map((bucketItem) => <li>{bucketItem}</li>);
return (
<>
<h1>버킷 리스트</h1>
// 3. 마지막으로 <ul>태그로 감싼 bucketList를 반환
<ul>{bucketList}</ul>
</>
);
};
export default List;
- 하지만 위의 예제를 실행하면 콘솔에서 리스트의 각 자식 요소들은 유일한 “key” prop을 가지고 있어야만 한다는 경고가 발생하는 것을 확인할 수 있다.
key prop 설정
- React에서는 엘리먼트 리스트를 만들 때 각 아이템마다 고유한 key를 지정해야 한다. 이러한 key를 통해 해당 리스트에 저장된 아이템 간의 식별을 안정적으로 수행하며, 어떤 아이템이 변경, 추가 또는 삭제되었는지를 빠르게 파악할 수 있다. 잘 선택된 key는 React가 리스트에 무슨 일이 일어났는지를 파악하고, DOM 트리를 정확하게 업데이트하는 데 많은 도움을 줄 수 있다.
- key 값은 같은 리스트에 포함된 아이템 사이에서만 고유한 값을 가지면 된다. 즉, 전체 애플리케이션이나 단일 컴포넌트 전체를 모두 통틀어 고유한 값일 필요는 없다. 따라서 다른 리스트에서 같은 key 값을 사용하는 것은 전혀 문제가 되지 않는다.
- 일반적으로 key는 해당 데이터가 가지고 있는 고유한 값(id)을 사용하는 것이 가장 좋다. 예를 들어, 데이터베이스로부터 가지고 온 데이터라면 DB의 key 값이나 id 값을 사용하는 것이 좋으며, 게시판 데이터라면 게시물 id 등을 key로 사용하는 것이 좋다.
- Math.random()과 같이 랜덤한 값을 key로 사용해서는 안 된다. key는 React가 아이템을 추가, 제거 또는 다시 정렬할 시기를 결정할 수 있도록 리렌더링 시 “안정적으로 식별 가능”한 값이어야 한다.
- 만약 렌더링한 엘리먼트에 대한 안정적인 고윳값이 전혀 없다면, 마지막 수단으로 다음 예제처럼 리스트의 index를 key로 사용할 수 있다.
// index 값을 key로 사용한 예시
const bucketList = bucketItems.map((bucketItem, index) => (
<li key={index}>{bucketList}</li>
));
- React에서는 리스트 아이템에 key를 명시적으로 지정하지 않으면, 기본적으로 index 값을 key로 사용한다. 하지만 index 값을 key로 사용하는 것은 애플리케이션의 성능 저하나 컴포넌트의 state와 관련된 문제가 발생할 수 있기 때문에 권장하지 않는다.
// List.js
// map() 메소드에 사용할 수 있도록 배열의 각 요소에 id 속성을 추가한 예제
// 더 이상 콘솔에서 경고가 발생하지 않음
const bucketItems = [
{ id: 0, text: "세계 일주 여행" },
{ id: 1, text: "스카이 다이빙" },
{ id: 2, text: "오로라 구경하기" },
{ id: 3, text: "마라톤 완주" }
];
const List = () => {
const bucketList = bucketItems.map((bucketItem) => (
<li key={bucketItem.id}>{bucketItem.text}</li>
));
return (
<>
<h1>버킷 리스트</h1>
<ul>{bucketList}</ul>
</>
);
};
export default List;
아이템 추가 기능 구현
- 앞선 예제에 state를 활용하여 배열에 새로운 요소를 추가하는 기능을 구현해 보자.
- React Hook은 함수 컴포넌트 내부에서만 호출할 수 있기 때문에 useState를 사용하기 위해서 배열을 List 컴포넌트 내부로 이동시킨다.
- 우리는 앞서 React에서는 참조 타입인 객체나 배열의 경우 반드시 불변성(Immutability)이 지켜져야만 한다고 배웠다. 따라서 배열에 새로운 요소를 추가할 때는 배열 객체의 push() 메소드를 사용해서는 안 된다. push() 메소드는 메소드를 호출한 원본 배열의 마지막부터 새로운 요소를 추가하므로 바로 이 불변성을 위반한다.
- 이와 같은 이유로 React에서는 push() 메소드 대신 concat() 메소드를 사용하여 새로운 요소가 추가된 새로운 배열을 생성하고, setItems() 함수를 호출하여 state를 업데이트해야만 한다.
- concat() 메소드는 메소드를 호출한 원본 배열의 마지막부터 인수로 전달받은 값들을 순서대로 추가한 새로운 배열을 생성하여 반환하므로, 원본 배열의 불변성이 지켜지게 된다.
array.concat([value1[, value2[, ...[, valueN]]]])
// List.js
import { useState } from "react";
const List = () => {
const [items, setItems] = useState([
{ id: 0, name: "세계 일주 여행" },
{ id: 1, name: "스카이 다이빙" },
{ id: 2, name: "오로라 구경하기" },
{ id: 3, name: "마라톤 완주" }
]);
const [inputText, setValue] = useState("");
const [id, setId] = useState(4); // 이제부터 추가되는 아이템의 key 값은 4부터 시작함.
const onChange = (e) => setValue(e.target.value);
const onClick = () => {
const newItems = items.concat({
id: id,
name: inputText
});
setItems(newItems);
setId(id + 1);
setValue("");
};
const bucketList = items.map((item) => <li key={item.id}>{item.name}</li>);
return (
<>
<h1>버킷 리스트</h1>
<input value={inputText} onChange={onChange} />
<button onClick={onClick}>추가하기</button>
<ul>{bucketList}</ul>
</>
);
};
export default List;
아이템 제거 기능 구현
- 마지막으로 리스트에서 제거하고자 하는 아이템 위에서 마우스를 우클릭하면 배열에서 요소를 제거하는 기능을 구현하자.
- 배열 객체의 filter() 메소드는 배열 내 각 요소에 대해 전달받은 콜백 함수를 호출하여, true 값을 반환하는 모든 요소들을 새로운 배열로 생성하여 반환한다.
arr.filter(callback(element[, index[, array]])[, thisArg])
// List.js
// filter() 메소드를 활용하면 특정 조건을 만족하는 요소들을 선택하거나, 특정 조건을 만족하는 요소들만을 손쉽게 제거
const onRemove = (id) => {
const newItems = items.filter((name) => name.id !== id);
setItems(newItems);
};
const bucketList = items.map((item) => (
<li
key={item.id}
onContextMenu={(e) => {
onRemove(item.id);
e.preventDefault();
}}
>
{item.name}
</li>
));
- onRemove() 함수에서는 filter() 메소드를 사용하여 인수로 전달받은 id 값에 해당하는 요소만을 제외한 새로운 배열을 생성한다. 이렇게 생성된 배열을 setItems() 함수에 전달하여 해당 요소가 제거된 새로운 배열을 newItems에 업데이트한다.
- 자바스크립트에서 마우스 우클릭 동작은 onContextMenu 이벤트로 제어할 수 있습니다. 하지만 마우스를 우클릭하게 되면 우클릭 이벤트와 함께 마우스 우클릭의 기본 동작인 컨텍스트 메뉴(context menu)도 함께 실행된다. 따라서 preventDefault() 메소드를 명시적으로 호출하여 마우스 우클릭의 기본 동작을 방지하고 있다.
Reference
반응형
'Development > ReactJs' 카테고리의 다른 글
[React] 리액트 라우터 - 개요, 설정, 중첩 라우팅, 추가 컴포넌트 및 Hook (0) | 2025.03.09 |
---|---|
[React] 리액트 Hooks - State, Context, Ref, Effect, Performance, Custom (1) | 2025.03.08 |
[React] 리액트 이벤트 핸들링 - 이벤트 핸들링 및 전파 (0) | 2025.03.07 |
[React] 리액트 컴포넌트 스타일링 - Css, Sass, Css Module, Styled-components (0) | 2025.03.07 |
[React] 리액트 데이터 관리 - Props, State (1) | 2024.11.19 |