Language/React

React props.children

conqueror-G 2022. 7. 1. 23:09

props.children

props.children은 컴포넌트를 재사용할 때 사용된다. 그 예시로서, 어느 웹 사이트나 있는 로그인과 회원가입을 예로 들 수 있다. 그 중에서도 왓챠피디아의 로그인 버튼을 눌렀을 때 나오는 모달 창과 회원가입을 눌렀을 때 나오는 모달창이 어딘가 비슷하다는 느낌을 받는다.

이 사진을 봤을 때, 나는 ❓ 상태였다. 어디가 달라진걸까? 나는 폰트의 크기가 미묘하게 다르다고 답했었는데, 흠흠..❌ 나만 그렇게 생각한게 아니었다

정답은 아니다 < 디자인 적인 요소가 아니라 UI를 봤을 때 비교해서 어느 부분이 반복되는지, 변화하는 요소는 무엇인지 체크해봐야한다. 먼저 변화하는 요소를 체크했다. 체크되지 않은 요소는 반복되는 요소이다.

이것을 글로 정리해보면…!!

회원가입

  • 모달창 레이아웃 (변화 없음)
  • 로고 (변화 없음)
  • 타이틀(회원가입) (변화 있음)
  • Input 박스(이름, 이메일, 비밀번호, 생년월일) (변화 있음)
  • 언어 선택(변화 있음)
  • 회원 가입 버튼 (변화 있음)
  • 계정 찾기 (변화 있음)
  • 소셜 로그인 버튼 (변화 없음)

로그인

  • 모달창 레이아웃 (변화 없음)
  • 로고 (변화 없음)
  • 타이틀(로그인) (변화 있음)
  • Input 박스(이름, 이메일, 비밀번호, 생년월일) (변화 있음)
  • 언어 선택 없어졌음 (변화 있음)
  • 로그인 버튼 (변화 있음)
  • 회원가입 문구 (변화 있음)
  • 소셜 로그인 버튼 (변화 없음)

변화가 없는 부분을 컴포넌트로 만들어본다.

ModalLayout이라는 컴포넌트를 만들고 기본적인 레이아웃이나 css를 작성해준다!

ModalLayout 컴포넌트.scss

.modalArea {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  background-color: #000;
  opacity: 0.2;
}

.modal {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-direction: column;
  position: absolute;
  top: 50%;
  left: 50%;
  width: 500px;
  height: 600px;
  padding: 20px;
  background-color: #fff;
  border: 0;
  border-radius: 20px;
  text-align: center;
  transform: translate(-50%, -50%);

  .whachaLogo {
    .watchaPointColor {
      color: rgb(245, 0, 0);
      font-weight: 700;
      font-size: 40px;

      &::after {
        display: inline;
        content: ' CLASSIC';
        color: #000;
      }
    }
  }
}

ModalLayout 컴포넌트.js

import React from 'react';
import './ModalLayout.scss';

const ModalLayout = ({ children }) => {
  return (
    <div className='modalLayout'>
      <div className='modalArea' />
      <section className='modal'>
        <h1 className='whachaLogo'>
          <span className='watchaPointColor'>WATCHA</span>
        </h1>
        {children}
      </section>
    </div>
  );
};

export default ModalLayout;

scss는 중요한게 아니니까 건너뛰고 컴포넌트.js를 보자. 여기서 props.children이라는 개념이 등장한다. 우리는 리액트를 넘어오면서 컴포넌트에 항상 self-closing을 해왔다. 스스로 닫는 태그를 이르는 말이다.

<ModalLayout />

하지만 props.children이라는 개념을 통해 우리는 레이아웃을 그리는 컴포넌트를 미리 만들어두고 그 컴포넌트로 다른 컴포넌트를 포함하거나 태그를 포함하면 해당 레이아웃 안에서 렌더링된다. 이를태면 이런 예시이다

import React from 'react';
import ModalLayout from './ModalLayout';
import './Modal.scss';

const Modal = () => {
  return (
    <ModalLayout>
      <h2>로그인</h2>
    </ModalLayout>
  );
};

export default Modal;

여기서 <h2>로그인</h2> 이 부분이 {children}에 작성된다!

우리는 이 개념을 이용해서 변하지 않는 부분과 변하는 부분을 컨트롤 할 수 있다!

Nav컴포넌트를 만들고 스타일을 입혀주자.

.nav {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 300px;
  height: 300px;
  border: 3px solid black;
  background-color: firebrick;

  .midWrapper {
    display: flex;
    justify-content: center;
    align-items: center;

    .signButton {
      background-color: silver;
      padding: 5px 20px;
    }

    .signInButton {
      margin-right: 10px;
    }
  }
}
import React from 'react';
import './Nav.scss';

const Nav = () => {
  return (
    <>
      <nav className='nav'>
        <div className='midWrapper'>
          <button className='signButton signInButton'>
            로그인
          </button>
          <button className='signButton signUpButton'>
            회원가입
          </button>
        </div>
      </nav>
    </>
  );
};

export default Nav;

네브 안에 로그인 버튼과 회원가입 버튼을 만들어주고 state를 만들어준다. 초기값은 비어있는 문자열이다. 비어있는 문자열은 falsy한 값이다. 왜 불린 값이 아니라 falsy한 값을 넣는지는 나중에 밝혀진다!!

const [modalStatus, setModalStatus] = useState('');

다음은 버튼을 클릭했을 때 실행할 함수를 만든다. 이 함수를 실행하면 버튼을 클릭한 이벤트 객체의 innerText값을 가져옴과 동시에 state의 내용을 innerText값으로 업데이트해준다!

const handleModal = event => {
    const { innerText } = event.target;
    return setModalStatus(innerText);
};

로그인 버튼의 innerText는 로그인! 회원가입 버튼의 inerText는 회원가입!

함수를 만들었다면 각 버튼에 동일한 이벤트 리스너를 작성하고, 미리 작성한 모달창을 네브 아래에 불러온다! 단! 조건부로….

import React from 'react';
import { useState } from 'react';
import Modal from './components/Modal/Modal';
import './Nav.scss';

const Nav = () => {
  const [modalStatus, setModalStatus] = useState('');

  const handleModal = event => {
    const { innerText } = event.target;
    return setModalStatus(innerText);
  };

  return (
    <>
      <nav className='nav'>
        <div className='midWrapper'>
          <button onClick={handleModal} className='signButton signInButton'>
            로그인
          </button>
          <button onClick={handleModal} className='signButton signUpButton'>
            회원가입
          </button>
        </div>
      </nav>
      {modalStatus && (
        <Modal
          title={modalStatus === '로그인' ? '로그인' : '회원가입'}
          handleModal={handleModal}
        />
      )}
    </>
  );
};

export default Nav;

modalStatus state가 초기 값으로는 falsy한 값이기 때문에 렌더링이 되지 않은 상태이고, 해당하는 버튼을 클릭했을 때 truthy한 값, 로그인(값이 입력된 문자열), 회원가입으로 업데이트되며, 모달창이 렌더링 된다! 또한 Modal 컴포넌트의 props로 title과 handleModal 함수를 전달한다.

그럼 Modal 컴포넌트로 돌아가자.

import React from 'react';
import ModalLayout from './ModalLayout';
import './Modal.scss';

const Modal = ({ title, handleModal }) => {
  return (
    <ModalLayout handleModal={handleModal}>
      <h2>{title}</h2>
    </ModalLayout>
  );
};

export default Modal;

Modal 컴포넌트에서는 props로 title과 handleModal이라는 함수를 전달받아 곳곳에 사용하는데, 우선 title은 변하는 데이터로서, 로그인창을 누르면 로그인이 title이 되고, 회원가입을 누르면 회원가입이 title이 되도록 한다.

handleModal이라는 함수는 ModalLayout으로 전달되며, 모달창의 바깥 부분을 사용자가 클릭했을 때 모달창이 닫히도록 하는 역할을 한다. 여기서 의아함이 들 수 있는데, 타당한 의아함이다! 이 handleModal이라는 함수를 다시 보자.

const handleModal = event => {
    const { innerText } = event.target;
    return setModalStatus(innerText);
};

클릭 이벤트가 실행됬을 때, modalStatus라는 state의 내용을 해당 이벤트 객체의 innerText로 업데이트해준다. 즉 바깥 부분을 클릭했을 때, 바깥 부분을 클릭한 태그의 innerText가 비어있는 문자열이기 때문에 모달창이 닫히는 원리이다.

사용자가 로그인 버튼을 클릭하면 로그인 버튼의 innerText는 로그인!

사용자가 모달창을 닫기위해 모달창 외부를 클릭했을 때 그 영역의 innerText는 비어있는 문자열!