이번 포스팅의 제목은 컴포넌트에 props를 전달하기 입니다. 하지만 이번 글은 제목처럼 단순히 props를 전달하는 것이 아닙니다. 컴포넌트에 props를 전달하는 과정은 복잡하기 때문입니다. 이제부터는 그동안 다루었던 컴포넌트의 정의, JSX의 규칙, 컴포넌트 렌더링 과정을 모두 알고 있어야 합니다. 이를 이용하여 컴포넌트에 props를 전달하는 과정을 알아보도록 하겠습니다.
또한 글의 길이와 가독성을 위해 전체 코드는 접은글로 작성할 것입니다. 필요하면 펼쳐보면 됩니다.
앞의 예제에서는 App.js에서 TodoTempmet 컴포넌트, TodoHead 컴포넌트, TodoMain 컴포넌트를 모두 관리했습니다. 이렇게 하나의 ..따라서 먼저 파일부터 나누어 보겠습니다.
1. props란?
먼저 props부터 알아봅시다. 컴포넌트에 전달하는 데이터를 props라고 합니다. properties의 줄임말입니다.
컴포넌트에 props를 전달하는 방법에는 크게 조건부 렌더링, 배열 렌더링이 있습니다.
2. 컴포넌트에 props 전달하기(기본)
props는 함수의 인수와 동일한 역할을 합니다. 함수와 다른 점은 컴포넌트는 props인자 하나만 받습니다. 여기서 props의 형태는 객체입니다. 따라서 아래와 같이 컴포넌트 내에서 props의 데이터를 가져올 때에는 객체에 접근하는 것과 같습니다. To Do List에서 할 일의 목록을 만들어주는 Task 컴포넌트를 만들며 자세히 알아봅시다.
하나의 데이터 전달하기
function Task(props) {
const task = props.name
return (
<div>{task}</div>
)
}
위의 코드에서는 컴포넌트에 name이라는 속성 하나를 props로 전달했습니다. 하지만 컴포넌트에서는 props를 객체 형태로 받기 때문에 name에 접근하기 위해서는 props.name으로 접근해야 합니다.
import React from 'react';
function Task(props) {
const task = props.name
return (
<div>{task}</div>
)
}
function TodoMain() {
return (
<div className='todoMainBox'>
<Task name="To Do 1"></Task>
<Task name="To Do 2"></Task>
<Task name="To Do 3"></Task>
</div>
)
}
export default TodoMain;
이제 TodoMain 컴포넌트에 name을 전달하여 실행해보면 아래와 같이 Task컴포넌트가 렌더링됩니다.
여러 데이터 전달하기(구조 분해 할당)
Task의 컴포넌트에 들어갈 데이터는 할 일 말고도 여러 가지가 필요합니다. 완료 여부도 props에 넣어보겠습니다. 그리고 완료 여부를 true, false로 전달하고 isDone의 값에 따라 출력 여부를 달리해보겠습니다.
import React from 'react';
//isDone의 값에 따라 연료 여부를 다르게 출력해주는 기능 추가
function Task(props) {
const task = props.name
let isDone = "진행 중"
if(props.done) {
isDone = "완료"
}
return (
<div>할 일: {task} 완료 여부: {isDone}</div>
)
}
//Task 컴포넌트에 isDone을 데이터 바인딩 형태로 전달
function TodoMain() {
return (
<div className='todoMainBox'>
<Task
name="To Do 1"
done={true}></Task>
<Task
name="To Do 2"
done={false}></Task>
<Task name="To Do 3"></Task>
<Task
name="To Do 2"
done={true}></Task>
<Task name="To Do 3"></Task>
</div>
)
}
export default TodoMain;
props.name에 접근했던것 처럼 propos.isDone에 접근하면 됩니다. 또한 Task컴포넌트에서는 데이터 바인딩의 형태로 isDone={true} 라인을 추가해주면 됩니다. 위의 코드를 실행하면 다음과 같이 완료 여부가 추가로 전달되어 나옵니다.
하지만 모든 데이터들을 .으로 접근하면 매우 불편합니다. 따라서 다음과 같이 props 대신 객체 데이터를 분해해서 전달할 수 있습니다. 이를 "구조 분해 할당(distructuring)"이라고 합니다.
function Task({name, done}) {
const task = name
let isDone = "진행 중"
if(done) {
isDone = "완료"
}
return (
<div>할 일: {task} 완료 여부: {isDone}</div>
)
}
props.name으로 접근하던것과 달리 name으로 한번에 접근할 수 있습니다. 코드가 매우 간결해졌습니다. 결과는 똑같습니다.
props의 기본값 지정하기
만약 Task 컴포넌트에 done을 전달하지 않았을 때 기본값을 지정하고 싶다면 다음과 같이 컴포넌트의 구조분해할당에서 값을 미리 지정해주면 됩니다.
import React from 'react';
//done에 속성이 전달되지 않았을 때 기본적으로 true로 설정
function Task({id, name, done=true}) {
...
}
return (
...
)
}
function TodoMain() {
return (
<div className='todoMainBox'>
<Task
name="To Do 1"
done={true}></Task>
<Task
name="To Do 2"
done={false}></Task>
<Task
name="To Do 3"
done={true}></Task>
<Task
name="To Do 4"
></Task>
</div>
)
}
export default TodoMain;
위의 코드를 실행해보면 네 번째 Task 에는 done이 전달되지 않았는데도 true의 기본값이 전달됩니다.
전개 구문으로 props 전달하기
아래와 같이 Task 컴포넌트에 할 일의 번호를 나타내는 데이터를 하나 더 추가해 보겠습니다. 그리고 출력 형태를 조금 바꿔보겠습니다.
function Task({id, name, done}) {
const task = name
let isDone = "진행 중"
if(done) {
isDone = "완료"
}
return (
<div>{id}. {task} 완료 여부: {isDone}</div>
)
}
이런식으로 데이터를 계속 추가하다보면 전달할 데이터도 점점 늘어납니다.
<Task
id={1}
name="To Do 1"
done={true}></Task>
이때 props에 전달할 데이터들을 미리 객체로 만들고 Javasacrip의 전개 구문을 이용하면 간편하게 전달할 수 있습니다. 다음처럼 말이죠!
const taskprops = {
id: 5,
name: "To Do 5",
done: false
}
<Task {...taskprops}></Task>
위의 코드를 실행시켜보면 마지막 To Do 5까지 잘 전달됩니다.
정리
아래의 코드는 위의 props의 전달 방법을 모두 적용해본 코드입니다. 한 번 살펴보세요.
전체 코드
import React from 'react';
function Task({id, name, done=true}) {
const task = name
let isDone = "진행 중"
if(done) {
isDone = "완료"
}
return (
<div>할 일{id}: {task} 완료 여부: {isDone}</div>
)
}
const taskprops = {
id: 5,
name: "To Do 5",
done: false
}
function TodoMain() {
return (
<div className='todoMainBox'>
<Task
id={1}
name="To Do 1"
done={true}></Task>
<Task
id={2}
name="To Do 2"
done={false}></Task>
<Task
id={3}
name="To Do 3"
done={true}></Task>
{/*Javascript done 전달 x*/}
<Task
id={4}
name="To Do 4"></Task>
{/*Javascript 전개 구문으로 전달한 props*/}
<Task {...taskprops}></Task>
</div>
)
}
export default TodoMain;
자식을 JSX로 전달하기
컴포넌트끼리 중첩할 수도 있습니다. 다음처럼 말이죠! 다음은 TodoListBox 컴포넌트 안에 Task 컴포넌트를 중첩시킨 코드입니다. 아래처럼 말이죠!
function TodoMain() {
return (
<div className='todoMainBox'>
<TodoListBox>
{/*TodoListBox 컴포넌트 안에 중첩된 Task 컴포넌트*/}
<Task
id={1}
name="To Do 1"
done={true}
></Task>
<Task
id={2}
name="To Do 2"
done={false}
></Task>
</TodoListBox>
</div>
)
}
이렇게 UI의 구조를 알아보기 위해 컴포넌트를 중첩시키기도 합니다. Task 컴포넌트는 TodoList 컴포넌트의 children으로 전달되죠. TodoList 컴포넌트의 구조는 아래와 같습니다.
function TodoListBox({children}) {
return (
<div className='todoListBox'>
{children}
</div>
)
}
이렇게 children으로 자식 컴포넌트한테 컴포넌트를 전달할 수 있습니다. TodoListBox 컴포넌트는 children 내부에서 무엇이 렌더링되는지 알 필요가 없습니다. TodoListBox 컴포넌트에 구멍을 뚫어놓고 틀을 만들어 놓은 것과 비슷합니다. 이렇게 하면 구조를 간단히 파악할 수 있다는 장점이 있습니다.
이 패턴은 컴포넌트 안에서 렌더링되는 UI의 형태가 다를 때 유용합니다. 다음과 같이 할 일의 완료 여부에 따라 렌더링되는 UI의 색이 다르게 해봅시다.
Task 컴포넌트의 props에 color속성을 추가하고 렌더링되는 UI의 속성에 넣어줍니다.
function Task({id, name, done=true, color}) {
let isDone = done
if(done) {
isDone = "완료"
}else{
isDone = "진행중"
}
return <div style={color={color}}>{id}. {name}, 완료 여부: {isDone}</div>
}
그리고 Task 컴포넌트의 속성으로 color값을 추가로 전달해주면 되겠죠. 전체 코드는 다음과 같습니다.
전체 코드
import React from 'react';
import './todoMain.css'
function Task({id, name, done=true, color}) {
let isDone = done
if(done) {
isDone = "완료"
}else{
isDone = "진행중"
}
return <div style={color={color}}>{id}. {name}, 완료 여부: {isDone}</div>
}
function TodoListBox({children}) {
return (
<div className='todoListBox'>
{children}
</div>
)
}
function TodoMain() {
return (
<div className='todoMainBox'>
<TodoListBox>
{/*TodoListBox 컴포넌트 안에 중첩된 Task 컴포넌트*/}
<Task
id={1}
name="To Do 1"
done={true}
color='red'
></Task>
<Task
id={2}
name="To Do 2"
done={false}
color='blue'
></Task>
</TodoListBox>
</div>
)
}
export default TodoMain;
3. 조건부 렌더링
조건부 렌더링
조건부 렌더링이란 원하는 조건에서만 요소들을 렌더링하는 것을 말합니다. 이를 적용하기 위해 To Do List에서 완료된 일은 del 태그로 렌더링하고 진행중인 일은 그대로 렌더링 해보겠습니다. 다음과 같이 말이죠!
위에서 다른 컴포넌트들은 그대로 두고 Task 컴포넌트만 수정하면 됩니다.
function Task({id, name, done=true, color}) {
if(done) {
return <div><del style={color={color}}>{id}. {name}</del></div>
}return <div style={color={color}}>{id}. {name}</div>
}
삼항 연산자 이용
위의 코드처럼 사용하기도 하지만 더 간단하게 하기 위해서 삼항 연산자를 사용합니다. 다음처럼 말이죠!
function Task({ id, name, done = true, color }) {
return (
done ? <div><del style={{ color: color }}>{id}. {name}</del></div> :
<div style={{ color: color }}>{id}. {name}</div>
);
}
전의 코드보다 더 간결해졌습니다.
논리 연산자 이용
삼항 연산자 말고도 논리 연산자를 이용할 수 있습니다. && 연산자를 이용하여 완료된 일만 표시해 보겠습니다.
function Task({ id, name, done = true, color }) {
return (
done && <div style={{ color: color }}>{id}. {name}</div>
);
}
위처럼 둘 다 참이어야 참이되는 &&(논리곱) 연산자를 이용하여 훨씬 더 간결하게 조건부 렌더링을 할 수 있습니다.
다음은 전체 코드입니다.
import React from 'react';
import './todoMain.css'
function Task({ id, name, done = true, color }) {
return (
done && <div style={{ color: color }}>{id}. {name}</div>
);
}
function TodoListBox({children}) {
return (
<div className='todoListBox'>
{children}
</div>
)
}
function TodoMain() {
return (
<div className='todoMainBox'>
<TodoListBox>
{/*TodoListBox 컴포넌트 안에 중첩된 Task 컴포넌트*/}
<Task
id={1}
name="To Do 1"
done={true}
color='red'
></Task>
<Task
id={2}
name="To Do 2"
done={false}
color='blue'
></Task>
</TodoListBox>
</div>
)
}
export default TodoMain;
4. 목록 렌더링
이전까지의 To Do List에서는 Task 컴포넌트를 하나씩 생성했습니다. 하지만 map함수를 이용하면 반복되는 컴포넌트들을 한 번에 렌더링할 수 있습니다. map()함수는 기본적으로 세 가지 인자를 전달받습니다.
- 콜백 함수:
2. 문법
- arr.map(callbackFunction, [thisArg])
- arr.map(callbackFunction(currenValue, index, array), thisArg)
출처: https://goddaehee.tistory.com/303 [갓대희의 작은공간:티스토리]
map함수를 사용하기 위해서는 먼저 배열을 만들어야 합니다.
To Do의 항목들을 예시로 만들어 보겠습니다. 이 Task들은 프론트 엔드에서 UI를 구성할 때 실제로 중요하게 생각하는 내용들입니다.
let todolists = [{
id: 1, //JSX에서 key로 사용됨.
name: "예외 처리하기",
done: true
}, {
id: 2,
name: "계층 구조 설계하기",
done: true
}, {
id: 3,
name: "UI 구현하기",
done: true
}, {
id:4,
name: "사용자와 상호 작용을 변수 설정",
done: false
}
]
그 다음 JSX를 이용하여 필요한 props을 넣어주면 됩니다.
function TodoListBox() {
return (
<div className='todoListBox'>
{todolists.map(todo => (
<Task key={todo.id}
id={todo.id}
name={todo.name}
done={todo.done}
color="green"
></Task>
))}
</div>
)}
여기서 중요한 점은 각각의 항목들은 고유한 key값을 가져야 합니다. 이 key들은 나중에 항목에 접근하는 기준이 됩니다.
실행하면 다음과 같이 렌더링되는 것을 볼 수 있습니다.
전체 코드
import React from 'react';
import './todoMain.css'
function Task({ id, name, done = true, color }) {
return (
done ? <div><del style={{ color: color }}>{id}. {name}</del></div> :
<div style={{ color: color }}>{id}. {name}</div>
);
}
let todolists = [{
id: 1, //JSX에서 key로 사용됨.
name: "예외 처리하기",
done: true
}, {
id: 2,
name: "계층 구조 설계하기",
done: true
}, {
id: 3,
name: "UI 구현하기",
done: true
}, {
id:4,
name: "사용자와 상호 작용을 변수 설정",
done: false
}
]
function TodoListBox() {
return (
<div className='todoListBox'>
{todolists.map(todo => (
<Task key={todo.id}
id={todo.id}
name={todo.name}
done={todo.done}
color="green"
></Task>
))}
</div>
)}
function TodoMain() {
return (
<div className='todoMainBox'>
<TodoListBox />
</div>
)
}
export default TodoMain;
이 코드들은 외우려고 하지 않아도 적용해보면서 이해해보면 익숙해 집니다. 중요한 것은 { }데이터 바인딩을 이용해 JSX에서 Javascript를 사용하는 방법입니다.
다음 포스티에서는 목록 렌더링과 컴포넌트 순수성에 대해 다루어 보겠습니다. 긴 글 읽어주셔서 감사합니다. 도움이 되었다면 공감 부탁드립니다.
'React' 카테고리의 다른 글
[React 톺아보기] 5. 이벤트 핸들러 (0) | 2024.03.03 |
---|---|
[React 톺아보기] 4. 컴포넌트(3) 컴포넌트 순수성 (0) | 2024.03.03 |
[React 톺아보기] 4. 컴포넌트(1) 컴포넌트 렌더링 (0) | 2024.02.27 |
[React 톺아보기] 3. JSX란? (0) | 2024.02.27 |
[React 톺아보기] 2. 엘리먼트 렌더링(렌더링 원리) (0) | 2024.02.27 |
경이로운 BE 개발자가 되기 위한 프로그래밍 공부 기록장
도움이 되었다면 "❤️" 또는 "👍🏻" 해주세요! 문의는 아래 이메일로 보내주세요.