React 기초3편

Props — 부모가 자식에게 건네는 속성

React의 Props를 웹의 속성(attribute)에 빗대어 정리합니다. 함수 인자처럼 동작하는 원리, 구조 분해, children, 그리고 'Props는 읽기 전용'이라는 규칙이 왜 존재하는지까지 짚습니다.

지난 글 끝에서 “컴포넌트를 조합할 때 부모가 자식에게 값을 건네는 방법”을 예고했습니다. 그게 바로 Props입니다. 사실 쉬워 보이는데 막상 뭔가 만들다 보면 의도대로 동작하지 않아 당황하게 만드는 대표 주자이기도 한데, 그 헷갈림의 대부분은 Props의 정체를 살짝 오해하는 데서 옵니다. 오늘 그 정체를 분명히 해두겠습니다.


Props는 컴포넌트의 ‘속성’이다

Props는 Properties, 즉 ‘속성’의 줄임말입니다. 이 이름이 전부라고 해도 과언이 아닙니다. 웹에서 우리는 이미 속성을 써 왔습니다.

<a href="https://...">링크</a>

여기서 hrefa 요소의 속성입니다. 태그에 성질을 부여해 그 요소가 어떻게 생기고 행동할지를 정하죠. React에서는 이 엘리먼트 자리에 컴포넌트가 들어옵니다. 그리고 컴포넌트는 함수이기 때문에, 속성은 그 함수의 인자처럼 전달됩니다.

function Welcome(props) {
  return <h1>안녕하세요, {props.name}님</h1>;
}

function App() {
  return <Welcome name="사라" />;
}

<Welcome name="사라" />라고 쓰면, Welcome 함수는 { name: "사라" }라는 객체를 props로 받습니다. 결과는 <h1>안녕하세요, 사라님</h1>이 되고요. 즉 JSX 속성으로 적은 것들이 하나의 객체로 묶여 컴포넌트 함수에 인자로 들어가는 구조입니다.


실무에서는 구조 분해로 꺼내 쓴다

props.name, props.age처럼 매번 props.를 붙이는 게 번거로워서, 실제로는 구조 분해(destructuring)로 필요한 것만 바로 꺼내 쓰는 경우가 훨씬 많습니다.

function Welcome({ name, age }) {
  return <h1>{name}님, {age}살이시군요</h1>;
}

동작은 완전히 같습니다. 인자로 들어온 props 객체를 함수 머리에서 곧바로 풀어 헤친 것뿐입니다. 기본값도 이 자리에서 줄 수 있습니다.

function Welcome({ name = "손님" }) {
  return <h1>{name}님, 환영합니다</h1>;
}

Props는 읽기 전용이다

가장 중요한 규칙 하나. 자식은 전달받은 Props를 고칠 수 없습니다. 읽기 전용(read-only)입니다.

function MyComponent({ name }) {
  name = "다른 이름"; // 🚩 이렇게 하면 안 된다
  return <h3>{name}</h3>;
}

왜 이런 제약이 있을까요? ‘잉크’에 ‘검정색’이라는 속성을 주어 ‘검정 잉크’로 정의했다면, 그 잉크가 스스로 자기 색을 빨강으로 바꿀 수는 없는 것과 같습니다. 속성은 그 컴포넌트가 바깥(부모)으로부터 부여받은 정체성입니다. 자식이 그걸 제멋대로 바꾼다면, 부모가 “너는 이런 모습이어야 해”라고 정해 준 약속이 깨지는 셈이죠.

이 규칙은 React의 큰 원칙인 단방향 데이터 흐름과 이어집니다. 데이터는 언제나 부모 → 자식, 위에서 아래로만 흐릅니다. 값이 한 방향으로만 흐르기 때문에, “이 값이 대체 어디서 바뀐 거지?”를 추적하기 쉬워지고, 지난 글들에서 말한 ‘예측 가능한 화면’이 가능해집니다.


children — 태그 사이에 낀 것도 Props다

속성뿐 아니라, 컴포넌트 여는 태그와 닫는 태그 사이에 넣은 내용도 Props로 전달됩니다. 이건 children이라는 특별한 이름으로 들어옵니다.

function Card({ children }) {
  return <div className="card">{children}</div>;
}

function App() {
  return (
    <Card>
      <h2>제목</h2>
      <p>본문</p>
    </Card>
  );
}

Card 안의 {children} 자리에 <h2><p>가 그대로 들어갑니다. 덕분에 바깥 껍데기(카드, 모달, 레이아웃 등)와 그 안에 담길 내용을 깔끔하게 분리할 수 있습니다.


Props를 ‘통신 수단’이라 부르지 말자

정리하면서 관점 하나를 바로잡고 싶습니다. Props를 흔히 “부모가 자식에게 값을 전달하는 통신 방법”이라고 설명하는데, 틀린 말은 아니지만 순서가 뒤바뀌어 있습니다.

Props의 본질은 어디까지나 자식 컴포넌트의 속성을 정의하는 것입니다. 그 속성을 정의하는 값을 부모가 내려주다 보니, 결과적으로 하향식 데이터 전달에도 쓰이는 것이지요. “값을 나르는 도구”가 아니라 “자식이 어떤 모습이어야 하는지 부모가 적어 주는 명세”로 보면, 앞머리에서 말한 ‘의도대로 동작하지 않는’ 순간이 확 줄어듭니다. 읽기 전용인 이유도 이 관점에서 자연스럽고요.

그런데 여기서 자연스러운 질문이 생깁니다. Props가 바깥에서 받아 바꿀 수 없는 값이라면, 컴포넌트가 스스로 들고 있으면서 시간에 따라 바뀌는 값은 어떻게 다룰까요? 다음 글의 주제, State가 바로 그것입니다.