props를 이용한 데이터 전달
이제 컴포넌트가 독립적인 UI 조각이라는 것을 이해했으니, 이 컴포넌트들이 서로 협력하여 하나의 완전한 애플리케이션을 만드는 방법을 배워야 합니다. 그 핵심적인 방법 중 하나가 바로 props
(프롭스) 를 이용한 데이터 전달입니다.
props란 무엇인가?
props
는 'properties'(속성)의 줄임말입니다. 리액트에서 props
는 부모 컴포넌트가 자식 컴포넌트에게 데이터를 전달할 때 사용하는 통로입니다. 마치 HTML 태그에 속성을 부여하듯이, 리액트 컴포넌트에도 속성을 부여하여 데이터를 전달할 수 있습니다.
props
의 주요 특징:
- 단방향 데이터 흐름:
props
는 항상 부모에서 자식으로, 한 방향으로만 전달됩니다. 자식 컴포넌트는 전달받은props
를 직접 변경할 수 없습니다. (읽기 전용) - 객체 형태:
props
는 항상 자바스크립트 객체 형태로 전달됩니다. - 컴포넌트의 재사용성 증대: 동일한 컴포넌트라도 어떤
props
를 전달하느냐에 따라 다양하게 변형하여 사용할 수 있어 컴포넌트의 재사용성을 극대화합니다.
레고 블록 비유를 다시 떠올려 볼까요? 부모 컴포넌트가 큰 레고판이라면, 자식 컴포넌트는 그 위에 얹을 작은 레고 블록입니다. props
는 이 작은 레고 블록의 색깔, 크기, 모양 등을 지정해주는 '설명서'와 같습니다. 설명서에 따라 블록을 조립하지만, 블록 자체가 설명서를 바꿀 수는 없는 것과 유사합니다.
props 사용하기: 부모에서 자식으로 데이터 전달
가장 기본적인 props
사용 예제를 통해 부모 컴포넌트가 자식 컴포넌트에 어떻게 데이터를 전달하는지 알아보겠습니다.
예제: UserProfile
컴포넌트 만들기
-
UserProfile
컴포넌트 생성:src
폴더 안에components
폴더를 만들고, 그 안에UserProfile.js
파일을 생성합니다.src/components/UserProfile.js // src/components/UserProfile.js import React from 'react'; function UserProfile(props) { // (1) props는 함수의 인자로 전달됩니다. console.log(props); // 전달받은 props 객체를 콘솔에 출력하여 확인해봅시다. return ( <div style={{ border: '1px solid #ddd', padding: '15px', margin: '10px', borderRadius: '8px' }}> <h3>이름: {props.name}</h3> {/* (2) props.속성명 으로 데이터에 접근 */} <p>나이: {props.age}세</p> <p>지역: {props.location}</p> {props.children && ( // (3) children props가 있다면 렌더링 <div style={{ marginTop: '10px', paddingTop: '10px', borderTop: '1px dashed #eee' }}> <h4>추가 정보:</h4> {props.children} </div> )} </div> ); } export default UserProfile;
- 함수형 컴포넌트에서
props
는 함수의 첫 번째 인자로 전달됩니다. props.속성명
형식으로 전달된 데이터에 접근할 수 있습니다.style
속성에{}
안에 또{}
가 있는 것은, 첫 번째 중괄호는 JSX에서 자바스크립트 표현식을 삽입하기 위함이고, 두 번째 중괄호는 CSS 속성을 담는 자바스크립트 객체를 의미합니다. (인라인 스타일)
- 함수형 컴포넌트에서
-
App.js
에서UserProfile
컴포넌트 사용:src/App.js
파일을 열고, 기존 내용을 수정하여UserProfile
컴포넌트를 불러와 사용합니다.src/App.js // src/App.js import React from 'react'; import './App.css'; import UserProfile from './components/UserProfile'; // UserProfile 컴포넌트 불러오기 function App() { const user1 = { name: '김리액트', age: 28, location: '서울' }; const user2 = { name: '박컴포넌트', age: 32, location: '부산' }; return ( <div className="App"> <h1>나의 사용자 프로필</h1> {/* UserProfile 컴포넌트에 props 전달 */} <UserProfile name={user1.name} age={user1.age} location={user1.location} /> {/* 또는 객체 전체를 전달할 수도 있습니다: <UserProfile {...user1} /> */} <UserProfile name="최프로퍼티" age={25} location="제주"> {/* 자식 요소로 직접 JSX를 전달 (props.children으로 접근) */} <p>특기: 농구</p> <p>취미: 독서</p> </UserProfile> <UserProfile {...user2}> {/* 스프레드 문법으로 객체 속성들을 props로 전달 */} <p>안녕하세요! 저는 {user2.name}입니다.</p> </UserProfile> </div> ); } export default App;
-
결과 확인: 개발 서버가 실행 중인 브라우저를 확인하면, 각각 다른 정보가 담긴
UserProfile
카드가 렌더링된 것을 볼 수 있을 것입니다. 개발자 도구(F12)를 열어 콘솔 탭을 확인하면UserProfile
컴포넌트에서console.log(props)
로 출력한props
객체의 내용도 확인할 수 있습니다.
위 예제에서 보듯이 props
는 HTML 속성처럼 name="값"
형태로 전달하며, 자바스크립트 변수나 표현식을 전달할 때는 name={변수명}
과 같이 중괄호 {}
를 사용합니다.
비구조화 할당으로 props 사용하기
props.name
, props.age
처럼 매번 props.
접두사를 붙이는 것이 번거로울 수 있습니다. 이럴 때 자바스크립트의 비구조화 할당(Destructuring Assignment) 문법을 사용하면 코드를 훨씬 간결하게 만들 수 있습니다.
UserProfile.js
파일을 다음과 같이 수정해 보세요.
// src/components/UserProfile.js (수정된 코드)
import React from 'react';
// props 객체에서 name, age, location, children 속성을 바로 추출
function UserProfile({ name, age, location, children }) {
return (
<div style={{ border: '1px solid #ddd', padding: '15px', margin: '10px', borderRadius: '8px' }}>
<h3>이름: {name}</h3> {/* props.name 대신 바로 name 사용 */}
<p>나이: {age}세</p>
<p>지역: {location}</p>
{children && (
<div style={{ marginTop: '10px', paddingTop: '10px', borderTop: '1px dashed #eee' }}>
<h4>추가 정보:</h4>
{children}
</div>
)}
</div>
);
}
export default UserProfile;
함수 인자에서 { name, age, location, children }
과 같이 작성하면, props
객체에서 해당 이름의 속성들을 추출하여 바로 변수로 사용할 수 있게 됩니다. 코드가 훨씬 깔끔해졌죠? 앞으로는 이 비구조화 할당 방식을 주로 사용하게 될 것입니다.
props.children
앞선 예제에서 UserProfile
컴포넌트 사용 시 <UserProfile> ... </UserProfile>
태그 사이에 다른 JSX 내용을 넣은 것을 보셨을 것입니다. 이렇게 컴포넌트 태그 사이에 위치하는 내용은 특별한 props
인 props.children
으로 전달됩니다.
UserProfile.js
에서 children && (...)
코드를 통해 props.children
의 존재 여부를 확인하고, 존재한다면 해당 내용을 렌더링하도록 했습니다. 이 props.children
은 컴포넌트가 마치 일반 HTML 태그처럼 내부에 콘텐츠를 포함할 수 있도록 해주어 매우 유연한 컴포넌트 설계가 가능하게 합니다.
props는 읽기 전용이다 (Read-Only)
앞서 props
의 특징으로 '단방향 데이터 흐름'과 '읽기 전용'임을 언급했습니다. 이는 자식 컴포넌트 내부에서 전달받은 props
의 값을 직접 변경해서는 안 된다는 의미입니다.
function MyComponent(props) {
// ❌ 이렇게 props 값을 직접 변경하려고 시도해서는 안 됩니다!
// props.name = '새로운 이름'; // 에러 발생 또는 예상치 못한 동작
return (
<div>
<p>이름: {props.name}</p>
</div>
);
}
리액트는 props
가 변경될 수 없다고 가정하고 최적화를 수행합니다. 만약 자식 컴포넌트에서 props
를 변경하면, 리액트의 단방향 데이터 흐름 원칙이 깨지고 애플리케이션의 상태를 예측하기 어려워져 복잡하고 디버깅하기 힘든 버그가 발생할 수 있습니다.
만약 자식 컴포넌트에서 부모로부터 받은 데이터를 변경해야 할 필요가 있다면, 다음과 같은 방법을 사용합니다.
- 부모 컴포넌트의 상태(State) 사용: 부모 컴포넌트가 관리하는 상태를 자식에게
props
로 전달하고, 자식은 부모가 전달해준 '상태 변경 함수' 를 호출하여 부모의 상태를 변경하도록 요청합니다. (다음 장에서state
에 대해 자세히 다룰 것입니다.) - 자식 컴포넌트 내부의 상태(State) 사용:
props
로 받은 데이터를 자식 컴포넌트의 초기 상태로 사용하고, 이후에는 자식 컴포넌트 내부에서 그 상태를 관리할 수 있습니다.
props
는 변하지 않는 데이터나, 부모가 자식에게 일방적으로 전달하는 정보를 표현할 때 사용한다고 생각하시면 됩니다.
PropTypes를 이용한 타입 검사 (권장)
대규모 프로젝트를 진행하거나 여러 개발자가 협업할 때, 특정 props
가 어떤 타입(문자열, 숫자, 불리언 등)이어야 하는지 명시하는 것이 중요합니다. 리액트에서는 prop-types
라이브러리를 사용하여 props
의 타입을 미리 지정하고 유효성 검사를 할 수 있습니다. 이는 개발 과정에서 잠재적인 버그를 미리 발견하는 데 도움을 줍니다.
-
prop-types
라이브러리 설치: 프로젝트 폴더에서 다음 명령어를 실행하여prop-types
를 설치합니다.npm install prop-types
-
UserProfile.js
에PropTypes
적용:src/components/UserProfile.js // src/components/UserProfile.js (PropTypes 추가) import React from 'react'; import PropTypes from 'prop-types'; // PropTypes 임포트 function UserProfile({ name, age, location, children }) { return ( <div style={{ border: '1px solid #ddd', padding: '15px', margin: '10px', borderRadius: '8px' }}> <h3>이름: {name}</h3> <p>나이: {age}세</p> <p>지역: {location}</p> {children && ( <div style={{ marginTop: '10px', paddingTop: '10px', borderTop: '1px dashed #eee' }}> <h4>추가 정보:</h4> {children} </div> )} </div> ); } // PropTypes 정의 UserProfile.propTypes = { name: PropTypes.string.isRequired, // name은 문자열이며 필수값입니다. age: PropTypes.number, // age는 숫자입니다. (필수 아님) location: PropTypes.string.isRequired, // location은 문자열이며 필수값입니다. children: PropTypes.node // children은 렌더링 가능한 모든 것 (문자열, 숫자, React 요소 등) }; // props가 전달되지 않았을 때 기본값 설정 (선택 사항) UserProfile.defaultProps = { age: 20, // age props가 전달되지 않으면 기본값으로 20 사용 }; export default UserProfile;
PropTypes.string.isRequired
:name
과location
은 문자열 타입이어야 하며, 반드시 전달되어야 합니다. 그렇지 않으면 콘솔에 경고 메시지가 나타납니다.PropTypes.number
:age
는 숫자 타입이어야 합니다.isRequired
가 없으므로 필수는 아닙니다.PropTypes.node
:children
은 숫자, 문자열, React 요소 등 렌더링 가능한 모든 것을 의미합니다.
이제 UserProfile
컴포넌트를 사용할 때, 예를 들어 name
을 전달하지 않거나 다른 타입을 전달하면 개발자 도구의 콘솔에 경고 메시지가 나타나 코드의 안정성을 높이는 데 도움을 줍니다.
2장 3절 "props를 이용한 데이터 전달"은 여기까지입니다. props
의 개념, 단방향 데이터 흐름, 비구조화 할당 사용법, children
props
, 그리고 props
가 읽기 전용이라는 중요한 규칙까지 상세하게 다루었습니다. PropTypes
는 선택 사항이지만 좋은 개발 습관을 위해 권장한다고 설명했습니다.
다음 장에서는 리액트 컴포넌트의 또 다른 핵심 개념이자 props
와 함께 컴포넌트의 동적인 상태를 관리하는 state
에 대해 알아보겠습니다.