폼의 응집도 생각하기
프론트엔드 개발을 하다 보면 Form으로 사용자에게 값을 입력받아야 하는 경우가 많아요. Form을 관리할 때는 2가지의 방법으로 응집도를 관리해서, 함께 수정되어야 할 코드가 함께 수정되도록 할 수 있어요.
필드 단위 응집도
필드 단위 응집은 개별 입력 요소를 독립적으로 관리하는 방식이에요. 각 필드가 고유의 검증 로직을 가지므로 변경이 필요한 범위가 줄어들어 특정 필드의 유지보수가 쉬워져요. 필드 단위의 응집도를 고려하여 설계하면, 각 필드의 검증 로직이 독립적이어서 다른 필드에 영향을 주지 않아요.
tsx
import { useForm } from "react-hook-form";
export function Form() {
const {
register,
formState: { errors },
handleSubmit
} = useForm({
defaultValues: {
name: "",
email: ""
}
});
const onSubmit = handleSubmit((formData) => {
// 폼 데이터 제출 로직
console.log("Form submitted:", formData);
});
return (
<form onSubmit={onSubmit}>
<div>
<input
{...register("name", {
validate: (value) =>
isEmptyStringOrNil(value) ? "이름을 입력해주세요." : ""
})}
placeholder="이름"
/>
{errors.name && <p>{errors.name.message}</p>}
</div>
<div>
<input
{...register("email", {
validate: (value) => {
if (isEmptyStringOrNil(value)) {
return "이메일을 입력해주세요.";
}
if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(value)) {
return "유효한 이메일 주소를 입력해주세요.";
}
return "";
}
})}
placeholder="이메일"
/>
{errors.email && <p>{errors.email.message}</p>}
</div>
<button type="submit">제출</button>
</form>
);
}
function isNil(value: unknown): value is null | undefined {
return value == null;
}
type NullableString = string | null | undefined;
function isEmptyStringOrNil(value: NullableString): boolean {
return isNil(value) || value.trim() === "";
}
폼 전체 단위 응집도
폼 전체 응집은 모든 필드의 검증 로직이 폼에 종속되는 방식이에요. 폼 전체에서의 흐름을 고려하여 설계되며, 변경 단위가 폼 단위로 발생할 때 고려해요.
폼 전체 응집도를 높이면, 폼 전체의 검증이 한 곳에서 관리되어 로직이 간결해지고, 상태가 중앙 집중식으로 관리되므로 폼 전체 흐름을 이해하기 쉬워져요. 필드 간의 결합도가 높아지므로 폼의 재사용성은 떨어질 수 있어요.
tsx
import * as z from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
const schema = z.object({
name: z.string().min(1, "이름을 입력해주세요."),
email: z
.string()
.min(1, "이메일을 입력해주세요.")
.email("유효한 이메일 주소를 입력해주세요")
});
export function Form() {
const {
register,
formState: { errors },
handleSubmit
} = useForm({
defaultValues: {
name: "",
email: ""
},
resolver: zodResolver(schema)
});
const onSubmit = handleSubmit((formData) => {
// 폼 데이터 제출 로직
console.log("Form submitted:", formData);
});
return (
<form onSubmit={onSubmit}>
<div>
<input {...register("name")} placeholder="이름" />
{errors.name && <p>{errors.name.message}</p>}
</div>
<div>
<input {...register("email")} placeholder="이메일" />
{errors.email && <p>{errors.email.message}</p>}
</div>
<button type="submit">제출</button>
</form>
);
}
필드 단위 vs. 폼 전체 단위 응집도
응집도를 높이려면 필드 단위와 폼 전체 단위 중 상황에 적합한 방식을 선택해야 해요. 필드 단위로 나누면 재사용성과 독립성이 높아지지만, 폼 전체 단위로 관리하면 일관된 흐름을 유지할 수 있어요.
변경의 단위가 필드 단위인지 폼 전체 단위인지에 따라 설계를 조정해야 해요.
필드 단위 응집도를 선택하면 좋을 때
- 독립적인 검증이 필요할 때: 필드별로 복잡한 검증 로직이 필요하거나 비동기 검증이 필요한 경우예요. 이메일 형식 검사, 전화번호 유효성 검증, 아이디 중복 확인, 추천 코드 유효성 확인처럼 각 필드가 독립적이고 고유한 검증이 필요할 때 유용해요.
- 재사용이 필요할 때: 필드와 검증 로직이 다른 폼에서도 동일하게 사용될 수 있는 경우예요. 공통 입력 필드들을 독립적으로 관리하고 재사용하고 싶을 때 좋아요.
폼 전체 단위 응집도를 선택하면 좋을 때
- 단일 기능을 나타낼 때: 모든 필드가 밀접하게 관련되어 하나의 완결된 기능을 구성하는 경우예요. 결제 정보나 배송 정보처럼 모든 필드가 하나의 비즈니스 로직을 이룰 때 유용해요.
- 단계별 입력이 필요할 때: Wizard Form과 같이 스텝별로 동작하는 복잡한 폼의 경우예요. 회원가입이나 설문조사처럼 이전 단계의 입력값이 다음 단계에 영향을 주는 경우에 적합해요.
- 필드 간 의존성이 있을 때: 여러 필드가 서로를 참조하거나 영향을 주는 경우예요. 비밀번호 확인이나 총액 계산처럼 필드 간 상호작용이 필요할 때 좋아요.