리엑트 JSX 조건문 렌더링

2023-05-11
ꞏ
유영진
ꞏ
show5116

공식문서
React의 jsx 문법에서는 중괄호 {} 내에는 자바스크립트의 표현식(Expression)이 들어갈 수 있습니다.

tsx
1const Plus = () => {
2 const num1 = 1;
3 const num2 = 2;
4
5 return (
6 <div>
7 <span>{num1}</span> + <span>{num2}</span> = <span>{num1 + num2}</span>
8 </div>
9 );
10};

하지만 문장(Statement)이 아닌 표현식이다 보니, if문이나 if-else문, switch문 등이 들어갈 수 없고, 또한 배열이 필요할 때 for문 또한 들어갈 수 없습니다.
따라서 jsx에서 어떠한 방법으로 조건식을 만들고 그걸로 렌더링을 표현하는지에 대해 알아보도록 하겠습니다.

함수 이용

복잡한 렌더링 규칙을 가질 때 사용하는 방법입니다.
if문 if-else문 등등.. 자유롭게 함수를 작성하고 해당 함수를 불러들이는 방법으로 구현이 가능합니다.

tsx
1interface OwnProps {
2 num1: number;
3 num2: number;
4 condition: '+' | '-' | '*' | '/';
5}
6
7const Calculator = ({ num1, num2, condition }: OwnProps) => {
8
9 const Result = () => {
10 if(condition = '+') {
11 return <span>{num1 + num2}</span>;
12 } else if(condition = '-') {
13 return <span>{num1 - num2}</span>;
14 } else if(condition = '-') {
15 return <span>{num1 * num2}</span>;
16 } else {
17 return <span>{num1 / num2}</span>;
18 }
19 }
20
21 return (
22 <div>
23 <span>{num1}</span>
24 <span>{condition}</span>
25 <span>{num2}</span>
26 <span>=</span>
27 <Result />
28 {/*혹은 {Result() } 로 표현하여도 같습니다.*/}
29 </div>
30 );
31};

해당 방법의 장점은 복잡한 연산이나 규칙이 필요할 때 함수로 관리를 함으로서 코드 관리가 용이해지고 구현하기도 쉬워집니다.
하지만 간단한 조건부 렌더링도 전부다 함수를 이용하면 코드가 길어지고 한눈에 보기 어려워 질 수 있습니다.

if문

간단한 if문에는 논리연산자 && 를 사용합니다.
&&은 논리곱으로 양쪽이 둘다 true 일 경우에만 true가 됩니다.
JavaScript에서 논리곱의 결과는 true && expression === true 가 되고, false && expression === false 가 됩니다.
예를들면 false && 'banana' === false 이고, true && 'banana' === 'banana'가 됩니다.
이러한 성질을 이용하여 if문 처럼 사용하여 렌더링을 수행할 수 있습니다.

tsx
1interface OwnProps {
2 admin: boolean;
3}
4
5const Main = ({ admin }: OwnProps) => {
6 return (
7 <div>
8 <Content />
9 {admin && <Admin />}
10 </div>
11 )
12}

위의 컴포넌트에서는 만약 admin값이 true이면 Admin컴포넌트가 렌더링 될 것 이고,
그 반대로 false라면 Admin컴포넌트는 렌더링 되지 않을 것입니다.

if-else문

if-else문에는 삼항연산자를 사용하여서 나타내줍니다.
로그인이 되어있을 땐 로그아웃 버튼을, 반대의 경우에는 로그인 버튼을 렌더링 할 수 있습니다.

tsx
1interface OwnProps {
2 login: boolean;
3}
4
5const LoginMenu = ({ login }: OwnProps) => {
6 return <div>{login ? <LogoutButton /> : <LoginButton />}</div>;
7};

for문

for문은 javascript의 Array.map() 메서드를 활용하여 구현해줍니다.
배열 내의 요소 각각에 대해 새로운 결과 배열을 반환하는 메서드입니다.
Array.forEach()와는 return값이 있나 없나 차이만 존재합니다.
arr.map(callback(currentValue[, index[, array]])[, thisArg]) callback함수의 인자로 currentValue와 index 그리고 array가 포함됩니다.

ts
1const fruits = [
2 apple: {
3 kor: '사과',
4 en: 'apple',
5 },
6 banana: {
7 kor: '바나나',
8 en: 'banana',
9 },
10];
11
12const korNames = fruits,map((fruit, index) => fruit.kor);
13
14console.log(korNames); // ['사과', '바나나']

위의 예시처럼 기존의 배열로 새로운 배열을 만들 수 있습니다.
JSX에서도 마찬가진데 배열을 이용해 React.ReactNode배열 결과를 만들어 냅니다.

tsx
1const nameList = ['철수','영희','길동'];
2
3const NameCard = () => {
4 return (
5 <div>
6 {nameList.map((name, index) => (
7 <span key={name}>{name}</span>
8 ))}
9 </div>
10 );
11}

Key

공식문서
표현식으로 배열 컴포넌트를 생성할 때 각종 IDE나 혹은 eslint에서 key를 넣어주지 않으면 경고문이 나옵니다.
key에는 문자열이 들어갈 수 있습니다.
key는 리스트 컴포넌트가 생성될 때 React에서 리스트이 요소들을 식별하기 위한 요소입니다.
다른 기능은 존재하지 않으며 오로지 리렌더가 일어날 때 해당 key의 컴포넌트들을 비교하며 효율적인 리렌더링을 수행합니다.
key를 사용할 때에는 조심해야할 점이 2가지 있습니다.

  • key는 형제끼리 같은 값을 가지면 안됩니다.
tsx
1const Example = () => {
2 const [list1, setList1] = useState([1,2,3,4,5]);
3 const [list2, setList2] = useState([5,6,7,8,9]);
4
5 return (
6 <div>
7 {list1.map((item) => (
8 <span key={item} onClick={onClickList1}>
9 {item}
10 </span>
11 ))}
12 {list2.map((item) => (
13 <span key={item}>{item}</span>
14 ))}
15 }
16 </div>
17 );
18
19 const onClickList1 = (event) => {
20 // 클릭된 요소를 삭제합니다.
21 setList1((prev) => prev.filter((element) => element !== event.target.innerText);
22 };
23}

만약 위와 같은 컴포넌트가 있을 때, 총 10개의 엘리먼트들이 생깁니다.
여기서 key값을 배열의 값으로 주었더니 5 라는 key값이 겹치게 됩니다.
React는 setState를 진행하고 리렌더가 일어날 때 key가 5인 엘리먼트가 사라져야 한다는걸 압니다.
하지만 key가 5인 엘리먼트는 2개가 있고, 그 결과로 어떻게 동작할지 예상할 수 없게 되버립니다.
❗같은 렌더 함수 내에서 두개 이상의 리스트 엘리먼트가 존재할 때 반드시 서로 다른 고유한 key값을 주어야 합니다.

  • index 사용을 지양해야 합니다.

리스트 엘리먼트가 한번 렌더된 후 변경사항이 아예 없다면 index를 key로 주어도 무방합니다.

tsx
1list.map((item, index) => <Item {...item} key={index} />);

하지만 위처럼 key에 index로 값을 부여하고 해당 리스트가 추가되거나 삭제되거나 순서가 바뀔 가능성이 있을 때 마찬가지로 사람이 예측하지 못한 동작이 일어날 수 있습니다.
그 이유는 React는 key값에 따라 컴포넌트의 변화점을 찾기 때문입니다.

배열이 존재할 때

Post라는 컴포넌트가 있을 때, 해당 컴포넌트는 댓글을 배열로 받게 될겁니다.
이럴 경우 댓글을 for문으로 렌더링 시켜줘야 합니다.
key값으로는 엘리먼트가 가진 고유한 id값을 주면 좋습니다.

tsx
1interface OwnProps {
2 title: string;
3 content: string;
4 comments: Comment[];
5}
6
7const Post = ({ title, content, comments }) => {
8 return (
9 <div>
10 <h2>{title}</h2>
11 <Content>{content}</Content>
12 {comments.map((comment, index) => (
13 <Comment index={index} key={comment.id}>
14 {comment}
15 </Comment>
16 ))}
17 </div>
18 );
19};

배열이 존재하지 않을 때

배열이 존재하지 않는 경우에는 Array(배열 숫자).fill(null).map((item) => … ) 이와 같은 형태로 배열을 생성해서 사용할 수 있습니다.
fill(value) 메서드는 배열의 값들을 value로 채워주는 역할을 합니다.
fill 메서드를 사용하지 않으면 배열의 값은 undefined 으로 되어있고, undefined는 map의 대상이 되지 않습니다.
따라서 메모리를 가지지 않는 null로 초기화 해주는게 좋습니다.

tsx
1interface OwnProps {
2 num: number;
3}
4
5const MultipleTable = ({ num }: OwnProps) => {
6 return (
7 <div>
8 {Array(9)
9 .fill(null)
10 .map((item, index) => (
11 <span key={`${num}${index}`}>{num * (index + 1)}</span>
12 ))}
13 </div>
14 );
15}

하지만 위의 방법에는 key로 고유한 데이터를 줄 수 없습니다.
배열의 변화 가능성이 있을 경우에는 이 방법은 좋지 않은 방법입니다.
배열을 새로이 만들어서 코드를 작성하는 것이 좋습니다.
커뮤니티에서는 일부는 uuid와 같은 난수를 사용해 key를 부여하는 방법도 좋다고 합니다.
하지만 개인적인 생각으로는 리렌더 될 때마다 새로운 uuid값을 제공해야 하고, 새로운 JSX element들을 만들어줘야 하니 비효율적 이라고 생각합니다.