React에서 대형 폼과 검증 로직 분리하기
리액트 대형 폼의 복잡도를 낮추고 유지보수성을 높이기 위한 설계 원칙과 검증 로직 분리 전략을 초보자도 이해하기 쉬운 예제로 정리한 자료
목차
서론: 왜 분리가 필요한가
대형 폼은 필드 수와 검증 규칙이 많아지면 금세 복잡해진다. JSX 파일에 모든 로직을 집어넣으면 가독성이 떨어진다. 또한 단위 테스트와 재사용이 어려워진다. 따라서 리액트에서 대형 폼 설계 시 폼 상태 관리와 form validation 분리 react 관점의 모듈화가 필요하다.
문제점 분석
1. 유지보수성 저하
폼과 검증 로직이 한 컴포넌트에 섞이면 변경 시 영향 범위를 파악하기 어렵다. 작은 수정이 예기치 않은 버그를 유발할 가능성이 크다.
2. 재사용 어려움
비슷한 검증 규칙을 여러 폼에서 반복 작성하면 중복이 생긴다. 재사용 가능한 검증 모듈이 없으면 개발 효율이 떨어진다.
설계 원칙
- 관심사의 분리: UI, 상태, 검증을 분리한다.
- 단일 책임: 각 모듈은 하나의 책임만 갖는다.
- 재사용성: 검증 로직은 프로젝트 전반에서 재사용 가능하도록 작성한다.
- 테스트 용이성: 단위 테스트가 쉬운 구조로 설계한다.
구조 제안
다음 구조는 실무에서 자주 사용된다.
- components/ : 순수 UI 컴포넌트
- hooks/ : 폼 상태와 비즈니스 로직을 다루는 커스텀 훅
- validators/ : 검증 규칙과 스키마
- services/ : API 호출 등 외부 의존
패턴별 설명
컨테이너-프리젠테이셔널 분리
컨테이너는 상태와 로직을 관리한다. 프리젠테이셔널은 입력 UI와 이벤트만 받는다. 이 방식은 리액트 폼 모듈화 방법과 잘 맞는다.
커스텀 훅 사용
useForm 같은 훅으로 상태와 폼 동작을 캡슐화하면 컴포넌트가 얇아진다. 훅 내부에서 검증 호출을 위임하면 로직 흐름이 명확해진다.
검증 모듈화
검증 로직은 validators 폴더에 분리한다. 룰을 함수로 만들고 스키마 형태로 조합하면 테스트와 재사용이 쉬워진다. 외부 라이브러리(예: Yup)를 사용하면 스키마 관리가 간편하다.
예제: 간단한 분리 구조
아래 예제는 커스텀 훅과 검증 모듈을 분리한 형태다. 초보자도 이해하기 쉬운 최소 구현으로 구성한다.
validators/validate.js
export function validateProfile(values) {
const errors = {};
if (!values.name || values.name.trim() === '') {
errors.name = '이름을 입력해야 합니다.';
}
if (!values.email) {
errors.email = '이메일을 입력해야 합니다.';
} else if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(values.email)) {
errors.email = '유효한 이메일 형식이 아닙니다.';
}
if (values.age && (values.age < 0 || values.age > 120)) {
errors.age = '나이가 유효하지 않습니다.';
}
return errors;
}
hooks/useForm.js
import { useState } from 'react';
import { validateProfile } from '../validators/validate';
export function useForm(initial) {
const [values, setValues] = useState(initial);
const [errors, setErrors] = useState({});
function handleChange(e) {
const { name, value } = e.target;
setValues(v => ({ ...v, [name]: value }));
}
function validate() {
const v = validateProfile(values);
setErrors(v);
return Object.keys(v).length === 0;
}
return { values, errors, handleChange, validate };
}
components/ProfileForm.js
import React from 'react';
import { useForm } from '../hooks/useForm';
export function ProfileForm() {
const { values, errors, handleChange, validate } = useForm({ name: '', email: '', age: '' });
function handleSubmit(e) {
e.preventDefault();
if (!validate()) return;
// API 호출 혹은 상위 컴포넌트로 데이터 전달
console.log('submit', values);
}
return (
<form onSubmit={handleSubmit}>
<input name="name" value={values.name} onChange={handleChange} />
<div>{errors.name}</div>
<input name="email" value={values.email} onChange={handleChange} />
<div>{errors.email}</div>
<input name="age" value={values.age} onChange={handleChange} />
<div>{errors.age}</div>
<button type="submit">저장</button>
</form>
);
}
확장과 테스트
검증 함수는 순수 함수로 유지하면 단위 테스트가 쉬워진다. 커스텀 훅은 React Testing Library의 renderHook으로 검증 가능하다. 새로운 필드가 생기면 validators만 수정하면 된다. UI 변경은 프리젠테이셔널 컴포넌트에 국한된다.
실무 팁 정리
- 검증 규칙은 데이터 중심으로 생각한다. UI 상태와 분리한다.
- 스키마 라이브러리 도입은 초기 비용이지만 대형 폼에서 이점이 크다.
- 중복 규칙은 공용 모듈로 추출한다.
- 상태는 최소한으로 유지하고 파생 값은 계산으로 처리한다.
결론
리액트 대형 폼 설계는 관심사 분리와 모듈화가 핵심이다. form validation 분리 react 방식으로 접근하면 코드가 명확해지고 유지보수가 쉬워진다. 위 구조는 시작점으로, 프로젝트 요구에 따라 훅과 검증 모듈을 확장해 적용하면 실무에 도움이 된다.