FrontEnd Develop/React Deep Dive

Props를 단순히 부모 - 자식 관계로만 치부할지말자.

Frisbeen 2025. 4. 4. 22:45

중복된 여러 코드가 보기 싫다에서 탄생된 것이 props다.

회원가입 form을 예시로, 아이디, 비밀번호, 뭐 등등 입력하는 칸

거의 완전 똑같이 생겼는데 이걸 굳이 3줄 겹치는 내용을 적어야한다면 그것은 비효율적이겠다.

import React, { useState } from 'react';

export default function SignupForm() {
  const [id, setId] = useState('');
  const [pw, setPw] = useState('');

  return (
    <div className="p-4 space-y-4">
      <div className="flex flex-col">
        <label className="mb-1 font-semibold">아이디</label>
        <input
          type="text"
          value={id}
          onChange={(e) => setId(e.target.value)}
          className="border p-2 rounded"
        />
      </div>

      <div className="flex flex-col">
        <label className="mb-1 font-semibold">비밀번호</label>
        <input
          type="text"
          value={pw}
          onChange={(e) => setPw(e.target.value)}
          className="border p-2 rounded"
        />
      </div>

      <button className="bg-blue-500 text-white px-4 py-2 rounded">
        회원가입
      </button>
    </div>
  );
}

구조가 완전히 같은 div가 몇번 반복되는것인가 DRY 원칙 위배

지금은 3개니까 그렇지 만약에 100개였으면 하

Dont repeat yourself

부모,자식 컴포넌트에서 둘다 쓰이는 공통 값(상태, 함수, 이벤트 핸들러,상태변경함수) 등등

현재 중복되고 있는 속성값은

type, value, onChange이라는 속성이다.

얘를 따로 빼볼까?

부모가 상태를 만들고 그 상태를 자식에게 전달하라.

자식 컴포넌트 (InputField.jsx)에 공통 속성값을 부여한다. (props)

줄때는 컴포넌트의 속성 처럼 부모에서 주고,

<InputField label="아이디" value={id} onChange={handleChange(setId)} />
<InputField label="비밀번호" value={pw} onChange={handleChange(setPw)} />

받을때는 자식 컴포넌트의 함수 매개변수에서 구조분해할당으로 받아라.

물론 여러가지 방식이 있긴하다

props 매개변수를 그냥 활용한다면

function InputField(props) {
  return <input value={props.value} onChange={props.onChange} />;
}

props로 매개변수

함수 내부에서 구조분해할당하는 것도 나쁘진 않다.

function InputField(props) {
  const { value, onChange } = props;
  return <input value={value} onChange={onChange} />;
}

함수 매개변수에서 구조분해 할당하는것이 가장 일반적인 방식

import React from 'react';

export default function InputField({ label, value, onChange }) {
  return (
    <div className="flex flex-col">
      <label className="mb-1 font-semibold">{label}</label>
      <input
        type="text"
        value={value}
        onChange={onChange}
        className="border p-2 rounded"
      />
    </div>
  );
}

구조분해할당했다고, props를 받았다고 무조건 그 값을 써야하는건 아님

디폴트 파라미터 활용가능 (근데 부모로 부터 직격으로 받는 값이 더 취지에 맞긴하겠다)

function InputField({ label = '입력', value, onChange }) {
  return <input placeholder={label} value={value} onChange={onChange} />;
}

실제 예시 : MJS PROJECT

로그인 페이지 리펙토링

사실 회원가입 폼이 되게 많아서, 얘로 위의 예시를 들어보려했는데 저저번주에 이미 refactoring을 custom Hook으로 해놓은 상태라 비교적 간단한 로그인 페이지 리펙토링을 진행한다.

UI/UX


배경: 반복되는 Input, 지겹다.

늘 이런 패턴이 많았던 것 같다.

<input type="text" placeholder="이메일" onChange={handleEmailChange} />
<input type="password" placeholder="비밀번호" onChange={handlePasswordChange} />
  • 구조가 너무 비슷함
  • type, placeholder, onChange만 다름
  • 하나만 바뀌면 복붙 지옥

그래서 이걸 공통 컴포넌트로 리팩터링하기로 결심했는데, 사실 커스텀 훅으로 하는것이 좀 더 적절하다고 생각이 들 수도 있지만 이렇게 간단한 리펙토링은 Props로 진행해도 된다고 판단하였다.


4가지 뼈대

  • Input을 컴포넌트로 분리해서 재사용
  • Emotion으로 스타일 관리 일관성 유지
  • props로 유연하게 값 전달 (type, placeholder, onChange)
  • 디자인 시스템 철학(atomic) 따라 구조화

공통 컴포넌트 코드: LoginInput.jsx

/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react';

const inputStyle = css`
  width: 100%;
  padding: 1rem;
  margin-bottom: 1.5rem;
  border: 1px solid #ddd;
  border-radius: 4px;
  font-size: 1rem;
  outline: none;

  &:focus {
    border-color: #001f5c;
    box-shadow: 0 0 5px rgba(0, 31, 92, 0.3);
  }
`;

const LoginInput = ({ type = 'text', placeholder, onChange, customStyle }) => {
  return (
    <input
      type={type}
      placeholder={placeholder}
      onChange={onChange}
      css={[inputStyle, customStyle]}
    />
  );
};

export default LoginInput;

문제 발생 : Emotion의 css prop은 일반 props처럼 전달 안 됨

<LoginInput css={someStyle} /> // ❌ 안 먹힘

Emotion은 css를 JSX 컴파일 시점에 자체 처리하므로, 자식 컴포넌트의 props로 css를 넘기면 무시됨.

굳이 emotion css까지 props로 넘길 필욘 없다고 판단하였다.

이모션 코드량도 상당하다. 이를 자식에게 분배하는것이 낫다고 판단했다.


사용 예시 (LoginPage.jsx) → 부모 컴포넌트에선 이렇게 뿌려주기만!

<LoginInput
  type="text"
  placeholder="이메일을 입력하세요."
  onChange={(e) => setEmail(e.target.value)}
/>

<LoginInput
  type="password"
  placeholder="비밀번호를 입력하세요."
  onChange={(e) => setPassword(e.target.value)}
/>


느낀 점 : 커스텀 훅으로 하는게 무조건 좋은가?

컴포넌트는 단순히 재사용이 목적이 아니다.

"변경에 강한 구조"를 설계하는 것 자체가 진짜 프론트엔드의 본질이라고 생각했다.

간단한 리펙토링을 굳이 복잡하게 벌려야하나? 라는 생각


Emotion이 까다롭긴 해도, 그만큼 정교한 제어가 가능하다. ‘

이번 리팩터링은 코드보다도 사고방식을 리팩터링하는 경험이었다.