정적 서식은 form이 어떤 특정 조건에 의해 생성되거나 하지 않으며, 내부의 input 종류도 고정적인 경우를 말합니다. 정적 서식의 대표적인 예시는 회원가입 form입니다. 이는 언제나 회원가입 버튼을 눌러 들어간 페이지에 존재하며, 그 구조도 email, password, nickname 등으로 늘 고정되어있습니다. 비슷하게 게시글 작성 form도 게시글 작성 페이지에 언제나 존재하며, 그 구조 역시 title, content, tags 등으로 고정되어있습니다.
이런 정적 서식을 작성할 때 저는 CreateForm에 initValue 혹은 validator 프로퍼티를 제공함으로써 타입 안전하게 form을 관리하도록 권합니다. CreateForm을 사용하면 컴포넌트 외부에서 form을 정의할 수 있습니다.
src/shared/signUpForm.ts
export const SIGN_UP_FORM = new CreateForm({
validator: {
email: {
required: { required: true, message: "Please enter your email" },
RegExp: {
RegExp: new RegExp("^[a-zA-Z0-9+-_.]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$"),
message: "Invalid email format",
},
},
nickname: {
required: { required: true, message: "Please enter your nickname" },
minLength: { number: 2, message: "Nickname must be at least 2 characters" },
maxLength: { number: 10, message: "Nickname must be 10 characters or less" },
},
password: {
required: { required: true, message: "Please enter your password" },
minLength: { number: 8, message: "Password must be at least 8 characters" },
maxLength: { number: 16, message: "Password must be 16 characters or less" },
RegExp: [
{
RegExp: new RegExp("^[^\s]+$"),
message: "Password cannot contain spaces",
},
{
RegExp: new RegExp("^(?=.*[a-z])(?=.*\d)(?=.*[@$!%*?&])[a-z\d@$!%*?&]+$"),
message: "Password must contain lowercase letters, numbers, and special characters",
},
],
},
passwordConfirm: {
required: { required: true, message: "Please enter your password" },
minLength: { number: 8, message: "Password must be at least 8 characters" },
maxLength: { number: 16, message: "Password must be 16 characters or less" },
RegExp: [
{
RegExp: new RegExp("^[^\s]+$"),
message: "Password cannot contain spaces",
},
{
RegExp: new RegExp("^(?=.*[a-z])(?=.*\d)(?=.*[@$!%*?&])[a-z\d@$!%*?&]+$"),
message: "Password must contain lowercase letters, numbers, and special characters",
},
],
custom: {
checkFn: (value: string, store: { password: unknown }) => value !== store.password,
message: "Passwords do not match",
},
},
"Check terms and conditions": {
checked: { checked: false, message: "Please agree to the terms and conditions" },
}
},
validateOn: ["submit", "change"],
clearFormOn: ["submit", "routeChange"]
});
이렇게 form의 스펙을 외부에서 정의하게 되면, 관심사의 분리가 명확해집니다. UI 컴포넌트와 폼 검증 로직이 분리되어 각 부분이 자신의 역할에만 집중할 수 있습니다. 폼의 복잡한 유효성 검사 규칙은 컴포넌트 외부에서 관리되므로, 컴포넌트 자체는 사용자 인터페이스에만 집중할 수 있습니다.
또한, 코드 가독성 측면에서도 큰 이점이 있습니다. 컴포넌트 내부가 복잡한 유효성 검사 로직으로 채워지지 않아 전체적인 구조를 파악하기 쉬워집니다. 실제 사용 예시를 보면 컴포넌트 코드가 간결하고 선언적이며, 폼 필드의 정의와 렌더링 로직이 명확하게 구분됩니다.