React 기초 [6] : Props & Memo

yoneeki·2023년 9월 22일

ReactBeginnerMovies

목록 보기
6/14

Props

  • 지난 번 작성했던 소스코드에서 우리는 최상위 컴포넌트인 App 안에 다른 컴포넌트를 집어 넣었다. 말 그대로 HTML을 캡슐화한 듯한 효과를 준 것이다.
  • 그러나 그 자식 컴포넌트들은 App으로부터 어떠한 데이터를 받거나 할 필요는 없이, 독립적으로 존재하였다.
  • 이러한 관점에서 시작하여 이제는 Prop에 대해 배워볼 것이다.

Props는 부모 컴포넌트로부터 자식 컴포넌트에 데이터를 보내는 어떠한 방식이다.

Props 구현

  • 우리가 회사에서 앱을 만들고 있다고 생각해보자. 다양한 버튼을 갖고 있을 것이다. 변경 사항 저장, 로그인, 확인, 취소 등등.
  • 대부분의 회사들은 이 버튼의 디자인은 완전히 동일하다. 그리고 우리는 기능이 다른 리액트 컴포넌트를 재사용 해야한다.
  • 그러나 여기까지 배운 지식만으로는 우리는 리액트 컴포넌트를 재사용할 수가 없다. 그래서 우리는 각각의 버튼마다 컴포넌트를 생성했다.
<script type="text/babel">
    function SaveBtn() {
      return (
        <button
          style={{
            backgroundColor: "tomato",
            color: "white",
            padding: "10px 20px",
            border: 0,
            borderRadius: "10px",
          }}
        >
          Save Changes
        </button>
      );
    }
    function ConfirmBtn() {
      return <button>Confirm</button>;
    }
    function App() {
      return (
        <div>
          <SaveBtn />
          <ConfirmBtn />
        </div>
      );
    }

    const root = document.getElementById("root");
    ReactDOM.render(<App />, root);
  </script>

  • 얘를 들어서 버튼의 디자인은 사실상 다 동일할 것인데, 이러한 코드에서는 똑같은 스타일에 대한 코드를 매 컴포넌트에 대해 직접 집어넣어야 한다. 아니면 저렇게 하나만 예쁜 모양을 가지게 될 것이다.
  • 무언가 다른 방법이 필요하다는 의미다. 스타일을 계속 코피페 할 수는 없으니!
  • 이런 스타일을 갖는 단 한 가지의 컴포넌트만 가지고 코드를 구현할 수는 없는 것일까? 오로지 버튼 태그 사이의 글자만 달라지면 되는데.


  • Props (Properties) 를 보낼 때는 어떤 이름이든 사용해도 상관없다. 그래서 banana="--" 같은 형식을 사용해 보았다.
  • Btn 함수에서 Props를 argument로 받아서 콘솔에 찍어보라고 했더니 저렇게 객체들이 잘 들어와서 찍힌다.


  • props.banana를 버튼 태그의 텍스트로 사용하라고 지정하면 이렇게 잘 사용이 된다.
  • 그러나 꼭 이렇게 코드를 적을 필요는 없다.
  • props가 객체인 점을 이용해보자.


  • Curly Brace로 Argument를 받으면, 객체의 어떤 값을 특정해서 가져오라고 할 수 있다.
<script type="text/babel">
    function Btn({ text, big }) {
      console.log("text : " + text + ", big : " + big);
      return (
        <button
          style={{
            backgroundColor: "tomato",
            color: "white",
            padding: "10px 20px",
            margin: "3px",
            border: 0,
            borderRadius: "10px",
            fontSize: big ? 18 : 10,
          }}
        >
          {text}
        </button>
      );
    }
    function App() {
      return (
        <div>
          <Btn text="Save Changes" big="true" />
          <Btn text="Continue" />
        </div>
      );
    }

    const root = document.getElementById("root");
    ReactDOM.render(<App />, root);
  </script>

  • 정상적인 코드처럼 보이게 하기 위해서 banana를 text로 변경하였다.
  • props는 여러개를 보낼 수 있기 때문에 불린값도 보내보았다.
  • 삼항연산자를 이용하여 폰트 사이즈 크기를 지정하였다.

Before getting into Memo

  • 나는 이제 버튼에 onClick 기능을 주고 싶다.
  • 이 onClick function은 App 컴포넌트에 있는 무언가의 State를 변경시킬 것이다.
function App() {
      const [value, setValue] = React.useState("Save Changes");
      const changeValue = () => setValue("Revert Changes");
      return (
        <div>
          <Btn text={value} onClick={changeValue} />
          <Btn text="Continue" />
        </div>
      );
    }
  • 그래서 나는 App 컴포넌트에다가 이렇게 코드를 수정해서 onClick 기능을 구현했다.
  • 그리고 우리는 여기서 이 녀석이 이벤트리스너라고 착각을 하게 된다. 아니다.
  • 이걸 내가 HTML 코드 안에 작성해야 이벤트리스너인거지, onClick={changeValue} 이 녀석은 놀랍게도 하나의 Prop이다.
  • onClick은 여기서 Prop의 이름이다.
  • 리액트가 실질적으로 이벤트리스너를 추가하는 게 아니라는 것을 이해해야 한다.
  • 예를 들자면, App 안에 있는 <Btn text={value} onClick={changeValue} /> 라인 안에다가 인라인 CSS... 라고 해야하나 아무튼 style={{}}을 작성해도 적용되지 않는다.
  • 생각해보면 쉽다. 쟤는 지금 Prop을 Btn 함수에다가 보내서 기능을 구현하고 있는데, 스타일을 바로 단다고 해서 프롭이 함수 안으로 제대로 전해지나 ?
  • 이런 것들을 세세히 이해하고 넘어가야 한다는 것이다.
  • 최상위 컴포넌트 안에 작성된 Btn 컴포넌트 안에 무언가를 작성한다는 것은, Btn 컴포넌트(함수) 안으로 Prop(데이터)을 보낸다는 것이다. 이 녀석들이 겉으로 HTML처럼 보인다고 해서 착각에 빠지게 되면 안 된다.
<script type="text/babel">
    function Btn({ text, onClick }) {
      console.log("text : " + text);
      return (
        <button
          onClick={onClick}
          style={{
            backgroundColor: "tomato",
            color: "white",
            padding: "10px 20px",
            margin: "3px",
            border: 0,
            borderRadius: "10px",
          }}
        >
          {text}
        </button>
      );
    }
    function App() {
      const [value, setValue] = React.useState("Save Changes");
      const changeValue = () => setValue("Revert Changes");
      return (
        <div>
          <Btn text={value} onClick={changeValue} />
          <Btn text="Continue" />
        </div>
      );
    }

    const root = document.getElementById("root");
    ReactDOM.render(<App />, root);
  </script>

  • 이렇게 onClick 기능 역시 Prop을 받아 실행하는 것이다.
  • 당연히 Prop의 이름은 커스터마이징이 가능하기 때문에 onClick이 아니라 또 banana나 apple이나 changeValue나 자기가 원하는 이름을 설정해서 보낼 수 있다.

결론적으로 말해 Prop으로 보낼 수 있는 데이터는 다양하다.
텍스트나 불린 값은 물론, 함수까지 보낼 수 있다는 말이다.
그리고 받은 Prop을 어디에 넣을지 결정하는 것은 개발자 자신이다.
리액트는 거기에 관여하지는 않는다.

function App() {
      const [value, setValue] = React.useState("Save Changes");
      const changeValue = () => setValue("Revert Changes");
      return (
        <div>
          <Btn text={value} onClick={changeValue} />
          <Btn text="Continue" />
        </div>
      );
    }
  • 그런데 App 부분을 다시 한 번 살펴보면 부모 컴포넌트에서 작성한 코드로 인해 부모 컴포넌트가 State 변경을 겪음을 알 수 있고, 또한 상태가 변경될 때 return 내의 HTML 요소들이 새로고침된다.
  • 즉 Save Changes 버튼을 눌렀을 때 말이다.
  • 그런데 우리는 부모 컴포넌트인 App의 상태를 바꾸는 함수를 만들었는데 그것을 실제로 실행하는 것은 자식 컴포넌트인 Btn이다.
  • 이런 세부사항까지 확실 집고 넘어가야 한다.

Memo

  • 그런데 우리는 return 안에 HTML들이 매번 리렌더링되는 상황을 원치 않는다.
  • 더 자세히 설명하자면, State가 바뀌는 것은 onClick이 있는 Btn 컴포넌트일 뿐인데 <Btn text="Continue" /> 까지 리프레시 되는 것이 별로라는 거다.
  • 이런 상황에서 우리는 React Memo라는 것을 사용할 수 있을 것이다.
  • 메모를 통해 컴포넌트를 리렌더링시킬 것인지 아닌지를 결정할 수 있다.
  • 그러므로 메모를 통해 첫 번째 버튼은 새로고침될 것이다. 왜냐하면 Props인 onClick이 State 변경과 밀접하게 연결되어 있기 때문이다. 하지만 그렇지 않은, 변경사항이 없는, 두 번째 버튼은 리렌더링되지 않을 것이다.
 function Btn({ text, onClick }) {
      console.log(text, "was rendered.");
      return (
        <button
          onClick={onClick}
          style={{
            backgroundColor: "tomato",
            color: "white",
            padding: "10px 20px",
            margin: "3px",
            border: 0,
            borderRadius: "10px",
          }}
        >
          {text}
        </button>
      );
    }

  • 메모 기능이 없을 때, 콘솔창을 보면 매번 렌더링이 발생함을 알 수 있다.
<script type="text/babel">
    function Btn({ text, onClick }) {
      console.log(text, "was rendered.");
      return (
        <button
          onClick={onClick}
          style={{
            backgroundColor: "tomato",
            color: "white",
            padding: "10px 20px",
            margin: "3px",
            border: 0,
            borderRadius: "10px",
          }}
        >
          {text}
        </button>
      );
    }

    const MemorizedBtn = React.memo(Btn);

    function App() {
      const [value, setValue] = React.useState("Save Changes");
      const changeValue = () => setValue("Revert Changes");
      return (
        <div>
          <MemorizedBtn text={value} onClick={changeValue} />
          <MemorizedBtn text="Continue" />
        </div>
      );
    }

    const root = document.getElementById("root");
    ReactDOM.render(<App />, root);
  </script>

  • 그렇게 놀랍게도 메모기능을 사용하니, 최초 브라우저에 나타날 때만 버튼 두 개가 모두 렌더링되고(1) 변경사항이 발생 됐을 때는(2) 컨티뉴 버튼이 렌더링되지 않는다.

메모 기능이 없을 때,
리액트의 최상위 컴포넌트(부모 컴포넌트)에서 그 어떤 부분에서 작은 State 변경 사항이 발생하기만 한다면, 모든 자녀들은 Re-render된다.
이로 인하여 어플리케이션이 매우 느려질 수 있는 것이다.
몇 천 개의 컴포넌트를 사용하는 큰 어플리케이션을 상상해보자.

Prop Types

<script type="text/babel">
    function Btn({ text, fontSize }) {
      return (
        <button
          style={{
            backgroundColor: "tomato",
            color: "white",
            padding: "10px 20px",
            margin: 3,
            border: 0,
            borderRadius: 10,
            fontSize: fontSize,
          }}
        >
          {text}
        </button>
      );
    }

    function App() {
      return (
        <div>
          <Btn text="Save Changes" fontSize={18} />

          <Btn text={18} fontSize={"Continue"} />
        </div>
      );
    }

    const root = document.getElementById("root");
    ReactDOM.render(<App />, root);
  </script>

  • 브라우저 상에 뜨긴 떴지만, 두 번째 버튼은 실수로 인하여 text와 fontSize의 값이 바뀐 것이 분명한 상황이다.
  • 그러므로 나는 지정되는 값을 정하고 싶다. text는 String, fontSize는 Number였으면 좋겠다.
  • 그런 우리를 위해 리액트 팀은 PropTypes라는 패키지를 만들어 주었다!
  • PropType은 내가 어떤 타입의 Prop을 받고 있는지 체크해 준다.

우선, 파일 안에 <script src="https://unpkg.com/prop-types@15.6/prop-types.js"></script> 를 설정!

 <script type="text/babel">
    function Btn({ text, fontSize }) {
      return (
        <button
          style={{
            backgroundColor: "tomato",
            color: "white",
            padding: "10px 20px",
            margin: 3,
            border: 0,
            borderRadius: 10,
            fontSize: fontSize,
          }}
        >
          {text}
        </button>
      );
    }

    Btn.propTypes = {
      text: PropTypes.string,
      fontSize: PropTypes.number,
    };
    
    function App() {
      return (
        <div>
          <Btn text="Save Changes" fontSize={18} />

          <Btn text={18} fontSize={"Continue"} />
        </div>
      );
    }

    const root = document.getElementById("root");
    ReactDOM.render(<App />, root);
  </script>

  • 그리고 PropTypes를 설정하면 저렇게 콘솔창에서 경고문을 볼 수가 있다.
  • 여러 명이서 엄청나게 많은 기능을 가진 긴 코드를 작성할 때, 내가 짠 코드를 보아야 하는 다른 개발자를 위해 추가할 수 있는 기능이기도 하고 나도 비슷하게 도움받을 수도 있는 것이다.
Btn.propTypes = {
      text: PropTypes.string,
      fontSize: PropTypes.number.isRequired,
    };

  • Prop의 Null을 막는 기능도 있다.
  • 이외에도 다양한 기능이 많다.


  • 저런 식으로 프롭이 null할 때 디폴트 값을 지정할 수도 있다.

PropType을 통해 오류가 고쳐지지는 않지만 Console로 Warning을 줄 수는 있다.

profile
Working Abroad ...

0개의 댓글