학습 자료

props — 부모가 자식에게 건네는 값


Article

지금까지 만든 컴포넌트는 항상 같은 화면만 그렸다. <Greeting />을 열 번 써도 열 번 다 똑같다.

상품 카드를 재사용하려면 상품마다 다른 이름과 가격을 넣어줘야 한다. 그 값을 props라 부른다.

함수의 매개변수와 똑같다

자바스크립트 편 10강에서 함수는 인자를 받았다. 컴포넌트는 함수이므로 당연히 인자를 받는다.

function Greeting(props) {
  return <p>안녕, {props.name}님</p>;
}

function App() {
  return (
    <>
      <Greeting name="지원" />
      <Greeting name="민수" />
    </>
  );
}

<Greeting name="지원" />에서 name="지원"이 인자다. 컴포넌트 안에서는 props.name으로 꺼낸다.

props객체 하나다. 넘긴 속성들이 전부 그 안에 담긴다.

<Greeting name="지원" age={27} isAdmin={true} />
컴포넌트가 받는 props 객체

속성 이름이 키가 되고, 값이 값이 된다

name
"지원"
age
27
isAdmin
true

구조분해로 꺼낸다

props. 를 계속 붙이면 지겹다. 자바스크립트 편 18강의 구조분해를 쓴다.

function Greeting({ name, age }) {
  return (
    <p>
      안녕, {name}님 ({age}세)
    </p>
  );
}

매개변수 자리에서 바로 분해했다. 실무 코드는 대부분 이렇게 생겼다. 이 컴포넌트가 무엇을 받는지 첫 줄만 보면 안다.

기본값도 그대로 쓴다.

function Greeting({ name = "손님", age = 0 }) {
  return <p>안녕, {name}님</p>;
}

<Greeting />이라고 쓰면 "손님"이 나온다. 자바스크립트 편 18강에서 본 대로 기본값은 undefined일 때만 쓰인다.

무엇이든 넘길 수 있다

문자열과 숫자만이 아니다.

function ProductCard({ product, onAdd, isNew }) {
  return (
    <div className="card">
      {isNew && <span className="badge">NEW</span>}
      <h3>{product.name}</h3>
      <p>{product.price.toLocaleString()}원</p>
      <button onClick={onAdd}>담기</button>
    </div>
  );
}

function App() {
  const laptop = { name: "노트북", price: 1200000 };

  return (
    <ProductCard
      product={laptop}
      onAdd={() => console.log("담았다")}
      isNew={true}
    />
  );
}

객체도, 배열도, 함수도 넘긴다. 자바스크립트 편 11강에서 "함수는 값"이라고 했으니 당연한 일이다. onAdd는 자식이 나중에 호출할 콜백이다.

넘기는 것쓰는 법비고
문자열name="지원"중괄호 없이 가능
숫자age={27}중괄호 필수
booleanisNew={true}isNew 만 써도 true
객체 · 배열product={obj}중괄호 필수
함수onAdd={handleAdd}괄호 없이 넘긴다

props 는 읽기 전용이다

가장 중요한 규칙이다. 자식은 받은 props를 절대 고칠 수 없다.

function Greeting({ name }) {
  name = "민수";
  return <p>안녕, {name}님</p>;
}

에러는 안 나지만 의미가 없다. 부모의 값은 그대로다. 다음 렌더링에서 다시 "지원"이 들어온다.

객체를 고치면 더 나쁘다.

function ProductCard({ product }) {
  product.price = 0;
  return <p>{product.price}원</p>;
}

자바스크립트 편 4강에서 본 참조 타입이다. 부모의 객체가 진짜로 바뀐다. 부모는 자기 데이터가 왜 바뀌었는지 모른다. 추적 불가능한 버그다.

데이터는 아래로만 흐른다

React의 핵심 원칙이다. 단방향 데이터 흐름이라 부른다.

props 는 위에서 아래로만
  1. App데이터를 가진 부모
  2. ProductList받아서 아래로 전달
  3. ProductCard받아서 화면에 그린다

자식이 부모의 데이터를 직접 바꿀 방법은 없다. 그럼 "담기" 버튼을 누르면 부모의 장바구니는 어떻게 바뀔까.

부모가 함수를 내려보낸다.

function App() {
  const handleAdd = name => console.log(`${name} 담김`);

  return <ProductCard name="노트북" onAdd={handleAdd} />;
}

function ProductCard({ name, onAdd }) {
  return <button onClick={() => onAdd(name)}>담기</button>;
}

데이터는 아래로, 이벤트는 함수를 통해 위로. 자식은 "이런 일이 일어났다"고 알릴 뿐, 무엇을 할지는 부모가 정한다.

흐름의 두 방향

데이터 ↓

props

  • 부모가 값을 준다
  • 자식은 읽기만 한다
  • name={laptop.name}

이벤트 ↑

콜백 함수

  • 부모가 함수를 준다
  • 자식이 그 함수를 부른다
  • onAdd={handleAdd}

이 구조 덕분에 값이 어디서 왔는지 추적할 수 있다. 화면이 이상하면 위로 거슬러 올라가면 된다.

props 를 통째로 넘긴다

같은 이름의 속성을 여러 개 넘길 때는 스프레드를 쓴다.

const product = { name: "노트북", price: 1200000, image: "/p1.png" };

<ProductCard {...product} />

// 위 한 줄은 아래와 같다
<ProductCard name="노트북" price={1200000} image="/p1.png" />

자바스크립트 편 18강의 스프레드다. 편하지만 무엇이 넘어가는지 보이지 않는다. 남용하면 컴포넌트가 무엇을 받는지 알 수 없어진다.

흔한 실수

자주 막히는 곳
  1. 01

    props 를 구조분해하지 않고 이름만 씀

    function Greeting(name) 은 틀렸다. 첫 인자는 항상 props 객체 하나다.

    function Greeting({ name }) { ... }
  2. 02

    숫자를 따옴표로 넘김

    count="3" 은 문자열이라 count + 1"31" 이 된다. 자바스크립트 편 7강의 형변환이다.

    <Counter count={3} />
  3. 03

    boolean 을 문자열로 넘김

    isNew="false" 는 비어 있지 않은 문자열이라 truthy 다. 항상 참이 된다.

    <Card isNew={false} />

세 번째가 특히 고약하다. isNew="false"는 화면에 배지를 계속 띄운다. 자바스크립트 편 7강에서 "0"도 truthy라고 했던 그 함정이다.

정리하면

  • props는 함수의 매개변수다. 컴포넌트는 props 객체 하나를 받는다.
  • 매개변수 자리에서 구조분해로 꺼낸다. 무엇을 받는지 한눈에 보인다.
  • 문자열이 아니면 중괄호로 넘긴다. age={27}, isNew={false}.
  • 함수를 넘길 때 괄호를 붙이지 않는다. 붙이면 즉시 실행된다.
  • props는 읽기 전용이다. 고치면 안 된다. 바뀌어야 하는 값은 state다.
  • 데이터는 아래로, 이벤트는 콜백으로 위로. 단방향 흐름이 추적을 가능하게 한다.

다음 강의에서는 태그 사이에 넣은 내용을 받는 특별한 props, children을 본다.

props — 부모가 자식에게 건네는 값 — 디코드랩(DCODELAB)