[React] ReactJS로 영화 웹 서비스 만들기 (4) props

hyobbang·2022년 9월 7일
0
post-thumbnail

4일차

4-0

props 이해

  • props가 필요한 상황
<!DOCTYPE html>
<html lang="en">
  <body>
    <div id="root"></div>
  </body>
  <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
  <script type="text/babel">
    const root = document.getElementById("root");

    function Btn() {
      return (
        <button
          style={{
            backgroundColor: "tomato",
            color: "white",
            padding: "10px 20px",
            border: 0,
            borderRadius: 10,
          }}
        >
          Btn
        </button>
      );
    }
    
    function App() {
      return (
        <div>
          <Btn />
        </div>
      );
    }

    ReactDOM.render(<App />, root);
  </script>
</html>

위와 같이 직접적으로 css를 준 버튼을 똑같은 스타일로 글자만 바꿔서 만들고 싶다면 어떻게 해야할까?
물론 css로 button 태그 전체에 스타일을 준다던지, 또는 class 값을 부여한다던지 해서 위와 같은 목적을 달성할 수도 있을 것이다.
그러나 일일이 class 값을 부여하는 건 귀찮은 방식이고, button 태그 전체에 스타일을 주는 게 부적절한 상황도 많을 것이다.

  • props 사용
function Btn(props) {
      console.log(props);
      return (
        <button
          style={{
            backgroundColor: "tomato",
            color: "white",
            padding: "10px 20px",
            border: 0,
            borderRadius: 10,
          }}
        >
          Btn
        </button>
      );
    }

    function App() {
      return (
        <div>
          <Btn hyobbang="save Changes" />
          <Btn hyobbang="Continue" />
        </div>
      );
    }

App에 만들어놨던 컴포넌트인 Btn을 두 번 써주었다.
버튼 두 개가 생겼고, 이것에 각각 hyobbang 이라는 사용자 정의 속성을 주입했다.
여기에 넣은 속성들은 Btn 컴포넌트의 인자, 즉 props로 받아올 수 있다.
이것이 props의 개념이다.

보다시피 props는 Object 형태로 넘어온다.
또한, 오직 첫 번째 인자로써만 존재한다.


function Btn(props) {
      console.log(props);
      return (
        <button
          style={{
            backgroundColor: "tomato",
            color: "white",
            padding: "10px 20px",
            border: 0,
            borderRadius: 10,
          }}
        >
          {props.hyobbang}
        </button>
      );
    }

    function App() {
      return (
        <div>
          <Btn hyobbang="save Changes" />
          <Btn hyobbang="Continue" />
        </div>
      );
    }

Btn 컴포넌트의 button 태그에 props에서 읽어온 hyobbang 속성을 적어주었다.

button이 원하는 대로 잘 바뀐 모습이다.

  • 더 간편한 props 사용법
function Btn({ hyobbang }) {
      return (
        <button
          style={{
            backgroundColor: "tomato",
            color: "white",
            padding: "10px 20px",
            border: 0,
            borderRadius: 10,
          }}
        >
          {hyobbang}
        </button>
      );
    }

굳이 props 라고 명시하지 않아도 된다.
파라미터가 들어가는 자리에 중괄호를 한 번 더 써주고 가져올 속성값을 적으면 한 번에 불러와서 사용할 수 있게된다.

  • prop 활용
function Btn({ hyobbang, big }) {
      return (
        <button
          style={{
            backgroundColor: "tomato",
            color: "white",
            padding: "10px 20px",
            border: 0,
            borderRadius: 10,
            fontSize: big ? 30 : 16,
          }}
        >
          {hyobbang}
        </button>
      );
    }

    function App() {
      return (
        <div>
          <Btn hyobbang="save Changes" big={true} />
          <Btn hyobbang="Continue" big={false} />
        </div>
      );
    }

hyobbang 뿐만 아니라 또다른 속성을 줘서 사용할 수도 있다.
여기서는 big 속성을 줘서 fontsize를 컨트롤 하고 있다.



4-1

props 활용 - 이벤트 넣기 / memo 기능

  • props에 이벤트 넣기
<!DOCTYPE html>
<html lang="en">
  <body>
    <div id="root"></div>
  </body>
  <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
  <script type="text/babel">
    const root = document.getElementById("root");

    function Btn({ text, changeValue }) {
      return (
        <button
          onClick={changeValue}
          style={{
            backgroundColor: "tomato",
            color: "white",
            padding: "10px 20px",
            border: 0,
            borderRadius: 10,
          }}
        >
          {text}
        </button>
      );
    }

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

    ReactDOM.render(<App />, root);
  </script>
</html>

이번 강의는 props의 활용에 대해서, 그 중에서도 이벤트를 제어하는 부분에 대해 진행됐다.
먼저, App 컴포넌트에 state를 추가했다.
const [value, setValue] = React.useState("Save Changes");
이 state는 첫 번째 Btn의 value를 컨트롤하는 녀석이다.
const changeValue = () => setValue("Revert Changes");
changeValue라는 함수를 만들어 value의 값을 바꿔주게 한다.


중요한 부분은 여기부터다.
<Btn text={value} changeValue={changeValue} />
이렇게 Btn에 changeValue 함수를 넣으면 잘 작동할까?
결론은, 그렇지 않다.
저 태그는 Btn 컴포넌트에 props를 추가하는 태그이다.
props는 결국 해당 컴포넌트에서 받아서 쓰지 않는다면 전혀 작동할 수가 없다.

function Btn({ text, changeValue }) {
      return (
        <button
          onClick={changeValue}
          style={{
            backgroundColor: "tomato",
            color: "white",
            padding: "10px 20px",
            border: 0,
            borderRadius: 10,
          }}
        >
          {text}
        </button>
      );
    }

이렇게 해당 컴포넌트에서 props를 제대로 받아서 들어가야할 위치, 즉 button 태그 안에 넣어주어야 이벤트가 잘 작동하게된다.



  • React memo 기능 사용하기
function Btn({ text, changeValue }) {
      console.log(text, "was rendered");
      return (
        <button
          onClick={changeValue}
          style={{
            backgroundColor: "tomato",
            color: "white",
            padding: "10px 20px",
            border: 0,
            borderRadius: 10,
          }}
        >
          {text}
        </button>
      );
    }

위와 같이 렌러링이 어떻게 되는지 확인하기 위해 콘솔에 적어본다면,

첫 렌더링에서는 이렇게 두 개의 Btn이 렌더링 되는 것을 확인할 수 있을 것이다.
그리고 Save Changes 버튼을 누른다면

만들어뒀던 changeValue state에 의해 value값이 변하면서 다시 Btn들이 리렌더링 되는 것을 확인할 수 있다.
그러나, 변경한 부분이 없는 Continue 버튼까지 리렌더링되고 있다.

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

react에서는 부모 컴포넌트의 어떤 state라도 변경이 일어나면 그 컴포넌트가 갖는 모든 자식 요소들이 리렌더링된다.
react의 인터렉티브한 요소였던 state는 잘못하면 성능악화를 가져올 수 있다.
만약 한 컴포넌트에 엮여있는 자식 컴포넌트가 1,000개라면? 1,000개의 요소들이 전부 리렌더링 된다.
생각만 해도 끔찍하다.
이런 상황을 방지해주는 기능이 memo이다.


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

const MemorizedBtn = React.memo(Btn); 이렇게 memo를 선언해주고, 파라미터에 적용할 컴포넌트를 넣어준다.
그리고 해당 컴포넌트가 사용되는 영역에 memo 기능이 들어간 컴포넌트 변수로 바꿔만 주면 쓸데없는 리렌더링을 방지할 수 있다.


버튼을 클릭해서 Rever Changes로 value값이 바뀌었으나, continue 버튼은 리렌더링 되지 않은 모습이다.
이렇듯, memo 기능은 props의 변화를 감지하여 컴포넌트 함수를 호출할지 말지를 결정해주는 아주 똑똑한 기능이다.



4-2

propTypes 기능

propTypes는 변수를 선언할 때 타입을 설정하지 않는 js의 특징에서 나오는 여러 문제점들을 보안해주는 기능이다.

예를 들어보겠다.

  • 문제 상황 - props의 type에 맞지 않은 값이 들어간 경우
    function Btn({ text, fontSize }) {
      return (
        <button
          style={{
            backgroundColor: "tomato",
            color: "white",
            padding: "10px 20px",
            border: 0,
            borderRadius: 10,
            fontSize,
          }}
        >
          {text}
        </button>
      );
    }
    
    function App() {
          const [value, setValue] = React.useState("Save Changes");
          const changeValue = () => setValue("Revert Changes");
          return (
            <div>
              <Btn text={value} fontSize={"18"} />
              <Btn text={12} />
            </div>
          );
        }

App 컴포넌트에서 text와 fontSize라는 props를 구성하여 Btn 컴포넌트에 변화를 주려했을 때, 누군가의 실수로 text에는 number 타입이, fontSize에는 string 타입이 들어간 상황이다.


java 였다면 에러를 띄웠겠지만, js에서는 별 문제없이 잘 돌아간다.
문제를 문제라고 인식하지 않는 이런 상황을 예방하기 위해 사용하는 것이 propTypes다.


  • propTypes를 통한 해결
<!DOCTYPE html>
<html lang="en">
  <body>
    <div id="root"></div>
  </body>
  <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
  <script src="https://unpkg.com/prop-types@15.7.2/prop-types.js"></script>
  <script type="text/babel">
    const root = document.getElementById("root");

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

    Btn.propTypes = {
      text: PropTypes.string,
      fontSize: PropTypes.number,
    };

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

    ReactDOM.render(<App />, root);
  </script>
</html>

위와 같이 코드를 작성하면 똑같은 상황에서 콘솔에 에러로그가 뜬다.

fontSize는 number 값을 원하는데, 현재 string으로 Btn에 공급되고 있다는 에러와
text는 string 값을 원하는데, 현재 number로 Btn에 공급되고 있다는 에러다.
이렇게 설정된 타입과 다른 값이 props에 들어왔을 때, 에러로그에 띄워주는 것이 propTypes의 기능이다.


  • proptypes 사용법

먼저, propTypes의 cdn을 추가해준다.
<script src="https://unpkg.com/prop-types@15.7.2/prop-types.js"></script>

그 다음에는 사용할 컴포넌트를 적고 .propTypes를 선언해준다.

 Btn.propTypes = {
      text: PropTypes.string,
      fontSize: PropTypes.number,
    };

propTypes 안에는 사용할 props를 적은 후 해당 props가 가져야할 타입을 설정해주면 된다.


propTypes 공식문서(한글버전)

import PropTypes from 'prop-types';

MyComponent.propTypes = {
  // prop가 특정 JS 형식임을 선언할 수 있습니다.
  // 이것들은 기본적으로 모두 선택 사항입니다.
  optionalArray: PropTypes.array,
  optionalBool: PropTypes.bool,
  optionalFunc: PropTypes.func,
  optionalNumber: PropTypes.number,
  optionalObject: PropTypes.object,
  optionalString: PropTypes.string,
  optionalSymbol: PropTypes.symbol,

  // 랜더링 될 수 있는 것들은 다음과 같습니다.
  // 숫자(numbers), 문자(strings), 엘리먼트(elements), 또는 이러한 타입들(types)을 포함하고 있는 배열(array) (혹은 배열의 fragment)
  optionalNode: PropTypes.node,

  // React 엘리먼트.
  optionalElement: PropTypes.element,

  // React 엘리먼트 타입 (ie. MyComponent)
  optionalElementType: PropTypes.elementType,

  // prop가 클래스의 인스턴스임을 선언할 수 있습니다.
  // 이 경우 JavaScript의 instanceof 연산자를 사용합니다.
  optionalMessage: PropTypes.instanceOf(Message),

  // 열거형(enum)으로 처리하여 prop가 특정 값들로 제한되도록 할 수 있습니다.
  optionalEnum: PropTypes.oneOf(['News', 'Photos']),

  // 여러 종류중 하나의 종류가 될 수 있는 객체
  optionalUnion: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number,
    PropTypes.instanceOf(Message)
  ]),

  // 특정 타입의 행렬
  optionalArrayOf: PropTypes.arrayOf(PropTypes.number),

  // 특정 타입의 프로퍼티 값들을 갖는 객체
  optionalObjectOf: PropTypes.objectOf(PropTypes.number),

  // 특정 형태를 갖는 객체
  optionalObjectWithShape: PropTypes.shape({
    color: PropTypes.string,
    fontSize: PropTypes.number
  }),

  // 추가 프로퍼티에 대한 경고가 있는 객체
  optionalObjectWithStrictShape: PropTypes.exact({
    name: PropTypes.string,
    quantity: PropTypes.number
  }),

  // 위에 있는 것 모두 `isRequired`와 연결하여 prop가 제공되지 않았을 때
  // 경고가 보이도록 할 수 있습니다.
  requiredFunc: PropTypes.func.isRequired,

  // 모든 데이터 타입이 가능한 필수값
  requiredAny: PropTypes.any.isRequired,

  // 사용자 정의 유효성 검사기를 지정할 수도 있습니다.
  // 검사 실패 시에는 에러(Error) 객체를 반환해야 합니다.
  // `oneOfType`안에서는 작동하지 않으므로 `console.warn` 혹은 throw 하지 마세요.
  customProp: function(props, propName, componentName) {
    if (!/matchme/.test(props[propName])) {
      return new Error(
        'Invalid prop `' + propName + '` supplied to' +
        ' `' + componentName + '`. Validation failed.'
      );
    }
  },

  // `arrayOf` 와 `objectOf 에 사용자 정의 유효성 검사기를 적용할 수 있습니다.
  // 검사 실패 시에는 에러(Error) 객체를 반환해야 합니다.
  // 유효성 검사기는 배열(array) 혹은 객체의 각 키(key)에 대하여 호출될 것입니다.
  // 유효성 검사기의 첫 두 개의 변수는 배열 혹은 객체 자신과 현재 아이템의 키입니다.

  customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
    if (!/matchme/.test(propValue[key])) {
      return new Error(
        'Invalid prop `' + propFullName + '` supplied to' +
        ' `' + componentName + '`. Validation failed.'
      );
    }
  })
};

Reference

https://ko.reactjs.org/docs/typechecking-with-proptypes.html

profile
매일 따끈따끈한 빵을 굽는 베이커리처럼 코딩하기

0개의 댓글