Props

Front-end/React 이론 스터디

Props

조커린 2021. 5. 13. 03:13

Props?

자식 컴포넌트가 부모 컴포넌트로 받아오는 값 ,

언제, 왜 쓰게 되는 걸까? (데이터의 전달)

자료 출처 : Components and Props - React \

특정한 상황에서 부모 컴포넌트에서 자식 컴포넌트로 특정한 값을 전달해서 렌더링해야할 시점이 있습니다.
이럴때 우리는 Props 를 사용하게 됩니다.

또한 리액트 애플리케이션내에서는 state값의 변경으로 리렌더링하는 로직을 호출하게 됩니다.
이로인해 전반적인 로직이 돌아가고 애플리케이션이 동작하게 됩니다.
state값을 서로 전달할 때에 유용하게 props를 사용하게 됩니다.

Props 전달과정 (React의 공식문서를 참고하였습니다.)

function Hello(props) { // -----> props = {name : "react"}
  return <div>안녕하세요 {props.name}</div>
}

ReactDOM.render( 
  <Hello name="react" />,
  document.getElementById('root')
);
  1. <Hello name="react" /> 를 요소로 ReactDOM.render() 를 호출합니다.
  2. React가 {name: 'react'} 를 props로 하여 Hello 컴포넌트를 호출합니다.
  3. Hello 컴포넌트가 그 결과로 <div>안녕하세요, react</div> 요소를 반환합니다.
  4. React DOM이 <div>Hello, react</div> 과 일치하도록 DOM을 효율적으로 업데이트합니다.

이렇게 Props는 전달되어 컴포넌트가 렌더링 됩니다.
props는 객체의 형태로 데이터, 함수등을 전달할 수 있고, 자식 컴포넌트에서는 이를 사용할 수 있습니다.

function Hello(props) { // -----> props = {name : "react"}
     return <div>안녕하세요 {props.name}</div> 
}

ReactDOM.render( // 1. 먼저 리액트 요소가 렌더링 됩니다. 렌더함수는 HTML DOM트리 내부에 전달받은 컴포넌트를 렌더링합니다.
    <Hello name="react" />, // 2,3. Hello 컴포넌트를 호출하여 <div>안녕하세요 {props.name}</div>를 리턴받음
  document.getElementById('root') ===>root돔태그 내부에 Hello 컴포넌트가 렌더링
);

<div id="root">

</div>

이제 비어있는 root태그가 아래와 같이 렌더링되어 바뀌게 됩니다.

// after
<div id="root">
    <div>
        안녕하세요 react
    </div>
</div>

Props는 읽기전용

자식 컴포넌트에서는 전달받은 Props를 변경할 수 없다고 (Immutable) 리액트 공식문서에서는 이를 정의하고 있습니다.
이는 리액트 개발자들이 변경이 예측가능할 수 있도록 의도해 개발했기 때문입니다.
props와 관련된 모든 함수는 '순수함수처럼' 동작해야 한다고 하고 있습니다.
(순수함수: 같은 input 에 대하여 같은 output, side effect 없음. 함수형 프로그래밍에서 중요하게 생각하는 개념이다. 결과가 무엇인지 예측할 수 있어야 하는 것.)
따라서 개발자는 props를 변경할 시에는 전달받은 setState나, state에 값을 복사해넣는 등의 방법으로 사용해야 합니다.

또 컴포넌트또한 이런 개념이 확장되어 순수함수 형식으로 작동한다고 합니다.
함수형 컴포넌트에서는 input은 props, output은 리턴값인 컴포넌트(ReactElement 트리)가 될 수 있습니다.
또한 클래스형 컴포넌트도 결국 받아온 render 메서드로 인해 컴포넌트를 리턴하기에 함수처럼 작동합니다.
( state는 constructor 의 this.state에 있습니다. )

하단의 인용글을 참고하였습니다.

"

실제로 리액트의 컴포넌트는 다른 객체지향 방식의 컴포넌트와는 다르게 동작한다. 객체간의 직접 참조나 메소드 호출을 통해서 서로간의 메시지를 주고받는 것이 아니며, 데이터의 흐름은 함수 호출처럼 오직 부모(호출하는 함수)에서 자식(호출되는 함수)을 향해 단방향으로 진행된다. 즉, 복잡한 ReactElement 트리를 구성하기 위해 컴포넌트 내부에서 다른 컴포넌트를 함수처럼 호출해서 결과값(ReactElement)를 받은 후 조합해서 반환할 뿐인 것이다.

"

출처 링크 : 리액트 HOC 집중 탐구 (1)

여러 개의 props, 비구조화 할당

props도 결국은 객체 형태로 내려오기 때문에 javascript의 문법을 이용하여 직접 해체가 가능합니다.
만약에 이런 형태의 color, name값을 전달시키는 구조가 있을때,

import React from 'react';
import Hello from './Hello';

function App() {
    // color="red", name="react"를 전달
  return (
    <Hello name="react" color="red"/> 
  );
}

export default App;

이때 자식 컴포넌트에서 컬러값을 뽑아와야 하는데요, props가 조금만 더 복잡해 져도 상당히 피곤한 일이 될 것입니다.

import React from 'react';

function Hello(props) {
  return <div style={{ color: props.color }}>안녕하세요 {props.name}</div>
}

export default Hello;

이럴 경우엔 비구조화 할당 (Destructuring)을 이용해 원하는 값을 뽑아올 수 있습니다.

Mozila문서에 이 문법에 대해 상당히 잘 나와있습니다.

구조 분해 할당 - JavaScript | MDN

defaultProps로 기본값 설정하기

props란 앞서 이야기 했듯이 함수형 컴포넌트의 파라미터 값으로 들어옵니다. 때문에 값이 있을수도, 없을 수도 있는데요.
만약 값이 없을 경우 defaultProps라는 프로퍼티로 설정할 수 있습니다.
( javascript 문법을 이용하여 컴포넌트의 default parameter로 설정할 수도 있습니다.)

  • Hello.js
import React from 'react';

//default parameter로 전달할 수도 있습니다.
function Hello({ color = "black", name }) {
  return <div style={{ color }}>안녕하세요 {name}</div>
}
//defaultProps property를 생성할 수도 있습니다. 
Hello.defaultProps = {
  name: '이름없음'
}

export default Hello;
  • App.js
import React from 'react';
import Hello from './Hello';

function App() {
  return (
    <>
      <Hello name="react" color="red"/>
      <Hello color="pink"/>{/* name prop 이 들어가지 않았습니다. 하지만 default prop이 작동합니다. */}
    </>
  );
}

export default App;

props.children

parameter로 들어간 값이 아닌 컴포넌트 태그 사이에 넣은 컴포넌트를 조회하고 싶을 때는 props.children을 사용하면 됩니다.
태그 사이에 컴포넌트가 들어갈 때 사용할 수 있는 값 입니다.
특정 컴포넌트 내부에서 다른 컴포넌트를 렌더링할 일이 있을 것입니다.
(리액트 공식문서에서는 Sidebar혹은 Dialog와 같은 컴포넌트에서 자주볼 수 있다고 설명하고 있습니다.)
이럴 때에 prop.children을 이용하여 그대로 전달하는 것이 좋습니다.

예시로 감싸는 div 태그를 상위 App.js에서 렌더링 하는 코드가 있을 때

  • Wrapper.js
    import React from 'react';

    function Wrapper() {
      const style = {
        border: '2px solid black',
        padding: '16px',
      };
      return (
        <div style={style}>

        </div>
      )
    }

    export default Wrapper;
  • App.js
   import React from 'react';
    import Hello from './Hello';
    import Wrapper from './Wrapper';

    function App() {
      return (
        <Wrapper>
          <Hello name="react" color="red"/>
          <Hello color="pink"/>
        </Wrapper>
      );
    }

    export default App;

위와 같이 만들게 되면 wrapper에는 아무것도 들어가지 않아 빈 컴포넌트만 렌더링 되게 됩니다.  
이럴 경우 props.children으로 사이에 있는 컴포넌트들을 받아줍니다.

 

import React from 'react';

function Wrapper({ children }) {
  const style = {
    border: '2px solid black',
    padding: '16px',
  };

//================= 컴포넌트를 전달 =================//
  return (
    **<div style={style}>
      {children}
    </div>**
  )
}

export default Wrapper;

 

또한 여러가지 레이아웃으로 받아온 컴포넌트를 구성하고 싶을 때에는 props로 컴포넌트를 전달하는 방법도 있습니다.

function SplitPane(props) {
  return (
    <div className="SplitPane">
      <div className="SplitPane-left">
        {props.left}
      </div>
      <div className="SplitPane-right">
        {props.right}
      </div>
    </div>
  );
}

function App() {
  return (
    <SplitPane
      left={
        <Contacts />
      }
      right={
        <Chat />
      } />
  );
}

 

이는 ReactElement가 객체이기 때문에 전달이 가능한 것이며 React에서 prop으로 전달할 수 있는 것이 제한이 없기 때문입니다.
React에서는 Props로 원시 타입의 값, ReactElement, 함수 등 어떠한 props도 받을 수 있다고 설명합니다.
하지만 props를 UI의 렌더링을 위한 목적 이외에 비즈니스로직을 위해 props를 사용하는 것은 권장되지 않습니다.
컴포넌트에서 재사용하기를 원한다면, React에서는 별도의 JavaScript모듈로 import하여 사용하는 것이 좋다고 React에서는 설명합니다. (상속받을 필요없이)

'Front-end > React 이론 스터디' 카테고리의 다른 글

API Fetching을 Context와 함께 써보자.  (0) 2021.06.04
CSS Module  (0) 2021.05.31
Context API  (0) 2021.05.29
useCallback  (0) 2021.05.26
List and Key  (0) 2021.05.16