icon
2장 : React 기초

props를 이용한 데이터 전달


이제 컴포넌트가 독립적인 UI 조각이라는 것을 이해했으니, 이 컴포넌트들이 서로 협력하여 하나의 완전한 애플리케이션을 만드는 방법을 배워야 합니다. 그 핵심적인 방법 중 하나가 바로 props(프롭스) 를 이용한 데이터 전달입니다.


props란 무엇인가?

props는 'properties'(속성)의 줄임말입니다. 리액트에서 props부모 컴포넌트가 자식 컴포넌트에게 데이터를 전달할 때 사용하는 통로입니다. 마치 HTML 태그에 속성을 부여하듯이, 리액트 컴포넌트에도 속성을 부여하여 데이터를 전달할 수 있습니다.

props의 주요 특징:

  • 단방향 데이터 흐름: props는 항상 부모에서 자식으로, 한 방향으로만 전달됩니다. 자식 컴포넌트는 전달받은 props를 직접 변경할 수 없습니다. (읽기 전용)
  • 객체 형태: props는 항상 자바스크립트 객체 형태로 전달됩니다.
  • 컴포넌트의 재사용성 증대: 동일한 컴포넌트라도 어떤 props를 전달하느냐에 따라 다양하게 변형하여 사용할 수 있어 컴포넌트의 재사용성을 극대화합니다.

레고 블록 비유를 다시 떠올려 볼까요? 부모 컴포넌트가 큰 레고판이라면, 자식 컴포넌트는 그 위에 얹을 작은 레고 블록입니다. props는 이 작은 레고 블록의 색깔, 크기, 모양 등을 지정해주는 '설명서'와 같습니다. 설명서에 따라 블록을 조립하지만, 블록 자체가 설명서를 바꿀 수는 없는 것과 유사합니다.


props 사용하기: 부모에서 자식으로 데이터 전달

가장 기본적인 props 사용 예제를 통해 부모 컴포넌트가 자식 컴포넌트에 어떻게 데이터를 전달하는지 알아보겠습니다.

예제: UserProfile 컴포넌트 만들기

  1. 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 속성을 담는 자바스크립트 객체를 의미합니다. (인라인 스타일)
  2. 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;
  3. 결과 확인: 개발 서버가 실행 중인 브라우저를 확인하면, 각각 다른 정보가 담긴 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
// 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 내용을 넣은 것을 보셨을 것입니다. 이렇게 컴포넌트 태그 사이에 위치하는 내용은 특별한 propsprops.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를 변경하면, 리액트의 단방향 데이터 흐름 원칙이 깨지고 애플리케이션의 상태를 예측하기 어려워져 복잡하고 디버깅하기 힘든 버그가 발생할 수 있습니다.

만약 자식 컴포넌트에서 부모로부터 받은 데이터를 변경해야 할 필요가 있다면, 다음과 같은 방법을 사용합니다.

  1. 부모 컴포넌트의 상태(State) 사용: 부모 컴포넌트가 관리하는 상태를 자식에게 props로 전달하고, 자식은 부모가 전달해준 '상태 변경 함수' 를 호출하여 부모의 상태를 변경하도록 요청합니다. (다음 장에서 state에 대해 자세히 다룰 것입니다.)
  2. 자식 컴포넌트 내부의 상태(State) 사용: props로 받은 데이터를 자식 컴포넌트의 초기 상태로 사용하고, 이후에는 자식 컴포넌트 내부에서 그 상태를 관리할 수 있습니다.

props는 변하지 않는 데이터나, 부모가 자식에게 일방적으로 전달하는 정보를 표현할 때 사용한다고 생각하시면 됩니다.


PropTypes를 이용한 타입 검사 (권장)

대규모 프로젝트를 진행하거나 여러 개발자가 협업할 때, 특정 props가 어떤 타입(문자열, 숫자, 불리언 등)이어야 하는지 명시하는 것이 중요합니다. 리액트에서는 prop-types 라이브러리를 사용하여 props의 타입을 미리 지정하고 유효성 검사를 할 수 있습니다. 이는 개발 과정에서 잠재적인 버그를 미리 발견하는 데 도움을 줍니다.

  1. prop-types 라이브러리 설치: 프로젝트 폴더에서 다음 명령어를 실행하여 prop-types를 설치합니다.

    npm install prop-types
  2. UserProfile.jsPropTypes 적용:

    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: namelocation은 문자열 타입이어야 하며, 반드시 전달되어야 합니다. 그렇지 않으면 콘솔에 경고 메시지가 나타납니다.
    • PropTypes.number: age는 숫자 타입이어야 합니다. isRequired가 없으므로 필수는 아닙니다.
    • PropTypes.node: children은 숫자, 문자열, React 요소 등 렌더링 가능한 모든 것을 의미합니다.

이제 UserProfile 컴포넌트를 사용할 때, 예를 들어 name을 전달하지 않거나 다른 타입을 전달하면 개발자 도구의 콘솔에 경고 메시지가 나타나 코드의 안정성을 높이는 데 도움을 줍니다.


2장 3절 "props를 이용한 데이터 전달"은 여기까지입니다. props의 개념, 단방향 데이터 흐름, 비구조화 할당 사용법, children props, 그리고 props가 읽기 전용이라는 중요한 규칙까지 상세하게 다루었습니다. PropTypes는 선택 사항이지만 좋은 개발 습관을 위해 권장한다고 설명했습니다.

다음 장에서는 리액트 컴포넌트의 또 다른 핵심 개념이자 props와 함께 컴포넌트의 동적인 상태를 관리하는 state에 대해 알아보겠습니다.