(React) 5. Children - props

19117 단어 ReactReact

잠깐! 시작하기 전에

이 글은 wecode에서 실제 공부하고, 이해한 내용들을 적는 글입니다. 글의 표현과는 달리 어쩌면 실무와는 전혀 상관이 없는 글일 수 있습니다.

또한 해당 글은 다양한 자료들과 작성자 지식이 합성된 글입니다. 따라서 포스팅이 틀린 정보이거나, 해당 개념에 대한 작성자의 이해가 부족할 수 있습니다.

설명하듯 적는게 습관이라 권위자 발톱만큼의 향기가 날 수 있으나, 엄연히 학생입니다. 따라서 하나의 참고자료로 활용하시길 바랍니다.

글의 내용과 다른 정보나 견해의 차이가 있을 수 있습니다.
이럴 때, 해당 부분을 언급하셔서 제가 더 공부할 수 있는 기회를 제공해주시면 감사할 것 같습니다.


서론

React를 본격적으로 사용하는 이유 중 하나인 Children, props입니다.

이상하게 크게 헷갈리는 부분은 없는데, 잘 사용하는지 모르겠는 애매한 개념인 것 같습니다.

사용하는데도 뭔가 옳지 않은, 석연치 않은 기분이 드는 개념입니다.

그래도 한 번 적으면서 복기해봅시다.

Children

React는 component 설계에 온 정신을 집중하게 되는 라이브러리입니다.

진짜 파면 팔수록 component 중심으로 사고하고 있는 나를 발견하게 해주는 녀석이죠.

children은 그러한 역할 중 하나를 담당합니다.

본격적으로 component를 나눌 수 있게 되는 개념이지 않을까 생각이 드네요.

보다 깊은 의미에서의 컴포넌트 분리

알다시피, 그간은 state가 있느냐, 없느냐, 혹은 가독성이 떨어지느냐, 아니냐 의 관점에서 컴포넌트를 나눠봤습니다.

근데 그건 정말 일차원적인, 어떻게 보면 양파의 가장 겉에 있는 껍질 정도를 분리하는 수준이었던 것이죠.

이제는 정말 재사용이 가능한가? 에 대한 물음부터 시작해야 합니다.

왜, why?

진짜로 한 컴포넌트가 수백개의 페이지를 구성할 수도 있기 때문입니다.

그렇다면, 그 기준은 어디서 나온거고, 무엇으로 이루어져있는지 생각해봅시다.

중복

우선 한 페이지를 봅시다.

실제 이번 clone 프로젝트에서 사용하고 있는 Login Page의 화면입니다.

그냥 보면 참 별거 없는데, 위 페이지는 전부 공용컴포넌트로 이루어진 view입니다.

Login 기능을 구현하기 위한 component의 경우 스타일과 아주 약간의 코드로 이루어져있습니다.

또한 상단의 nav와 footer의 경우 모든 페이지에 나타나는 컴포넌트입니다.

그럼, 다른 페이지를 볼까요?

이번에는 SignUp Page입니다.

언뜻 보기에도 Login과 SignUp은 상당히 비슷한 구조로 이루어져있고, 실제로 동일한 기능을 합니다.

  1. input value를 가지고 form에서 제출하며,

  2. 제출 시점에서는 백엔드의 DB로 post요청을 보냅니다.

  3. state는 당연히 각 페이지의 최상위 컴포넌트에서 일괄적으로 관리되며,

  4. 나머지는 전부 props로 전달받아 그려지는 구조입니다.

이 정도의 기능을 가진 페이지는 앞으로 수백개가 되어도 문제 없이 만들어낼 수 있습니다.

똑같은 nav, footer, input, form 구조라면 말이죠.

이게 React의 핵심입니다.

기존의 바닐라에 비해 상대적으로 적은 코드를 가지고 대량 생산, 관리할 수 있게 되는 것.

그리고 이 개념의 밑바닥에는 '중복'이 있습니다.

말 그대로 중복되는 요소는 전부 컴포넌트화 할 수 있습니다.

그 과정에는 children과 props 설계가 있겠습니다.

props 설계

앞서 말씀드렸던 children은 사실 디테일의 영역입니다.

기본 뼈대는 props로 모든 것이 가능한 상황이어야 합니다.

다시 말해, 분리된 컴포넌트는 props로만 구성되어야하고, 이는 제 기준에서 최대 중복 요소들의 집합입니다.

두 페이지를 구성하는 컴포넌트, 세 페이지를 구성하는 컴포넌트, 혹은 100개의 페이지를 구성하는 컴포넌트에서 가장 많이 중복되는 요소를 찾아내는게 먼저입니다.

login 과 signup의 경우 input과 form이 중복되고, nav와 footer는 전체 페이지에 중복입니다.

nav와 footer를 먼저 만들어둔 상태로 가정한다면, 이제 input과 form을 분리해야 합니다.

단, 특정 기능을 수행하는 컴포넌트의 경우 조금 까다로울 수 있습니다.

요구하는 기능이 다를 때, 동일한 컴포넌트로 바라볼 것인가? 에 대한 기준이 명확하지 않다고 생각하기 때문입니다.

예로, 저는 input의 onChange를 사용할 것인데, 만약 keyUp이나 keyDown을 사용하더라도 구성 가능하게 만들려면 해당 이벤트를 모두 집어넣은 하나의 공용 컴포넌트를 만들 것 같습니다.

잡설은 잠깐 넣어두고, 코드를 가져오겠습니다.

import React from 'react';

import './CommonInput.scss';

class CommonInput extends React.Component {
  render() {
    const { type, placeholder, handleOnChange } = this.props;

    return (
      <label className="commonLabel">
        <input
          type={type}
          placeholder={placeholder}
          onChange={handleOnChange}
        />
      </label>
    );
  }
}

export default CommonInput;

자, 보시는 것과 같이 상당히 별거 없는 뼈대의 형태입니다.

전부 props로 받고 있으며, 해당 props는 input에서 요구하는 최소한의 속성들입니다.

그럼 input 태그를 쓰면 되는데, 뭐하러 분리하지?

하는 생각을 했었으나, 알다시피 중복을 줄이는 것은 대부분의 코드에서 추구해야 할 미학입니다.

또한 코드의 길이를 줄이기 위해 노력하는 수많은 사람들이 떠오르며 생각을 잠시 넣어두게 되었습니다.

그리고 무엇보다도, 이렇게 컴포넌트를 구성해버리면 실제로 어떻게 state를 변경시키게 할지 고민할 수 있게 됩니다.

간단한 메서드를 input component 내부에서 작성하는게 아니라, 어떻게 전달해야 할지를 고민할 수 있습니다.

그런 점에서 학습에도 도움이 되었던 것 같습니다.

결과적으로,

props로 구성된 하나의 컴포넌트를 만들고, 어느 페이지에서나 동일한 기능을 수행할 수 있도록 한다.

가 바로 공용 컴포넌트의 핵심이었습니다.

이제, 디테일한 영역은 그럼 어떻게 관리할지 생각해봅시다.

Children? this.props.children?

예상외로 많이 헷갈리는 부분일거라 생각합니다.

그러나 명백히 말하자면, this.props.children은 import되는 컴포넌트 내에 위치해야 하며, 불러오는 곳에서 작성되는 구문의 내용이 children입니다.

뭔가 이상하죠?

this.props. 의 의미를 떠올리면, 전달받아진 것 중 ~ 입니다.

즉, 전달받은 객체 중에서 children 에 해당하는 것이기 때문에, 전달하는 곳인 부모 컴포넌트에서 작성되는 내용 전체가 children입니다.

이걸 어떻게 전달할까요?

import React from 'react';
import { withRouter } from 'react-router';

import './CommonForm.scss';

class CommonForm extends React.Component {
  render() {
    const { cases, handleSubmit } = this.props;
    return (
      <div className="CommonFormBlock">
        <h1 className="CommonFormTitle">{cases}</h1>
        <form className="CommonForm" onSubmit={handleSubmit}>
          {this.props.children}
          <button className="CommonFormButton">{cases}</button>
        </form>
      </div>
    );
  }
}

export default withRouter(CommonForm);

이렇게 받아올 준비를 마치고,

import React from 'react';
import { withRouter } from 'react-router-dom';
import CommonForm from '../../../component/Form/CommonForm';
import CommonInput from '../../../component/Input/CommonInput';
import { LOGIN_INPUT_LIST } from './LOGIN_INPUT_LIST';
import { LOGIN_API } from '../../../config';

import './LoginForm.scss';

class LoginForm extends React.Component {
  
  ...
  
  render() {
    const loginInpuList = LOGIN_INPUT_LIST.map(({ id, name, ...rest }) => {
      const { setLoginInfo } = this.props;

      return (
        <CommonInput
          key={id}
          name={name}
          {...rest}
          handleOnChange={e => {
            setLoginInfo(name, e.target.value);
          }}
        />
      );
    });

    const { loginSubmit } = this;
    const { userInfo } = this.props;

    return (
      <CommonForm
        type="signIn"
        cases="로그인"
        userInfo={userInfo}
        handleSubmit={loginSubmit}
      >
        {loginInpuList}
        <div className="findId">
          <span>아이디 찾기</span>
          <span>비밀번호 찾기</span>
        </div>
      </CommonForm>
    );
  }
}

const TOKEN_KEY = 'loginToken';

export default withRouter(LoginForm);

이런 방식으로 전달합니다.

input의 정보를 가진 상수 배열에 map() 을 실행시켜 <CommonInput /> 으로 변경한 뒤, 하나의 배열에 담아 <CommonForm />에서 render 하는 것이죠.

이 때, 아이디 찾기, 비밀번호 찾기 등과 같은 .findId 계층의 element들은 모두 children이 됩니다.

또한 input의 개수는 회원가입이 4개나 더 많기 때문에, 회원가입 페이지에서 동일한 form 컴포넌트 내에 다른 input 4개를 구현할 수 있으려면 input은 별개의 컴포넌트로 구성되어야 하기에 children에 해당합니다.

즉, 중복 요소를 가지고도 어떻게 구성을 다르게 하느냐.

이게 바로 children가 있어야만 하는 이유가 됩니다.

같은 form 컴포넌트와 스타일을 공유하지만(스타일링을 다르게 하려면 따로 빼는 방법도 있습니다.), Login과 SignUp은 분명 다른 구성으로 이루어져 있습니다.

구성이 다르나, 요소가 같다면 props와 children을 적절하게 조합해서 두 페이지를 쉽게 만들 수 있겠죠.

또는 구성도 동일하고, 요소 또한 전부 같다면 form component 하나만으로 더 쉽게 여러개의 페이지를 만들수도 있을겁니다.

근데 그런 페이지는 거의 없습니다.

정리하자면,


  1. 동일한 요소는 component( = input)로 분리하여 props로 구성할 수 있게 만든다.

  2. 다른 요소는 무시한 채로, 상위 계층, 즉, 공용 부모 역할의 component( = form)를 하나 만든다.

  3. 공용 부모 component( = form)에서 children( = this.props.children)을 받을 수 있게 구성한다.

  4. 공용 부모 component( = form)를 불러오는 최상위 component( = SignUp, Login)에서 디테일( = Children)을 완성한다.


이런 과정입니다.

제가 이해한 것이 맞을지 모르겠네요.

요점은 중복과 중복 중 가장 많은 요소, 그리고 디테일은 Children 3가지 입니다.

마치며

오늘은 비교적 쉬운데 쉽지 않은 내용이었습니다.

아무래도 프로젝트 진행중이라 개념 공부를 따로 더 할 시간이 없네요 😭

다음 글은 아마도 ImageSlider 부수기입니다.

바닥부터 구현하는 과정을 담아볼테니, 한 번 보시는것도 나쁘지 않겠습니다.

그럼 오늘은 여기서 마치겠습니다. 읽어주셔서 감사합니다.

좋은 웹페이지 즐겨찾기