들어가며
최근 프로젝트에서 폼 요소로 구성된 문의하기 팝업을 구현하는 과정에서 react-hook-form을 사용하게 되었습니다. 작업이 끝나고 코드 리팩토링 작업을 하면서 유효성 및 에러 처리를 좀 더 편리하게 도와주는 라이브러리 중 yup 대해 소개하려고 합니다.
Yup 소개하기
yup는 데이터의 유효성 검증을 객체 스키마로 선언하여 검증하는 라이브러리입니다. yup 설치 시 react-hook-form을 연결해주는 라이브러리도 함께 설치해줘야 합니다.
* 스키마란?
데이터베이스의 구조와 제약 조건에 관한 전반적인 명세를 기술한 메타데이터의 집합을 의미.
// 1. 설치
npm install yup @hookform/resolvers
// or
yarn add yup @hookform/resolvers
// 2. 선언
import { useForm } from 'react-hook-form';
import * as Yup from 'yup';
const resolver = useYupValidationResolver(validationSchema)
const { handleSubmit, register } = useForm({ resolver })
yup를 사용하려면 스키마 즉, 구문 작업과 입력값에 대한 테스트 코드에 대한 정의를 객체 정의를 해야 합니다. 스키마는 yup.object() 함수를 사용하여 정의할 수 있습니다.
object 스키마 정의
yup은 typescript를 지원하여 각 필드의 타입을 지정할 수 있고, 메서드의 나열형식으로 간결하고 확장할 수 있게 작성할 수 있습니다.
타입 정의하기
- string() / number() / date() / object() /boolean() / array()
데이터 구성하기
- required() / optional() : 해당 필드가 반드시 존재해야 하는 필수 요소 인지 / 선택 요소인지 체크
- nullable() : 필드의 데이터가 null 값을 허용하는지 체크
- default() : 필드의 출력값이 undefined인 경우 기본값 설정
- min / max : 필드의 최소 / 최대값을 설정
데이터 변환하기
- trim() : 텍스트 필드의 양쪽 공백을 제거
- lowercase() : 텍스트 필드의 경우, 영문을 모두 소문자로 변환
- uppercase() : 텍스트 필드의 경우, 영문을 모두 대문자로 변환
- ensure() : 텍스트 필드의 경우, undefined or null인 경우 빈 문자열로 변환
- calmelCase() / constantCase() : object 필드의 경우, 객체의 key 값을 camelCase / CONSTANT_CASE 형식으로 변환
데이터 검증하기
- match() : react-hook-form의 rules에 들어가는 값으로, 정규식 패턴을 통해 값의 형식을 검사
- email() / url() / uuid () : 정규식을 선언하지 않고, 내장된 정규식을 통해 유효성을 검증
- positive() / negative() / interger() / moreThan() / lessThan() : 숫자 필드는 양수 / 음수 / 정수 / 최댓값 / 최솟값을 검증
데이터 에러 처리
선언된 데이터 구성 코드의 메서드의 첫 번째(입력 인자가 없는 경우) / 두 번째 인자에 커스텀 에러 메시지를 설정할 수 있습니다.
required('이름은 필수 요소입니다.'),
min(3, '최솟값은 3입니다.')
matches('/^[0-9]+$/', '나이는 숫자만 입력 가능합니다.')
email('올바른 이메일 주소를 입력해주세요.')
positive('양수만 입력 가능합니다.')
Yup 적용하기
- yup 적용 전
컴포넌트에 필요한 UI 정보와 유효성 검사 관련 코드를 한곳에서 관리
// 스키마 정의
const formArray = [
{
name: "name",
label: "이름",
type: "text",
placeholder: "이름을 입력해 주세요.",
required: {
value: true,
message: "필수 입력 항목입니다."
},
regex: {
value: (/^[a-zA-Zㄱ-ㅎㅏ-ㅣ가-힣]{1,30}$/),
message: "한글, 영어만 입력할 수 있습니다.",
},
maxLength: {
value: 30,
message: "최대 30자 이내로 작성할 수 있습니다."
}
},
{
name: "phoneNumber",
label: "전화번호",
type: "text",
placeholder: "전화번호를 입력해 주세요.",
regex: {
value: (/^\d{1,11}$/g),
message: "숫자만 입력할 수 있습니다."
},
maxLength: {
value: 11,
message: "최대 11자 이내로 작성할 수 있습니다."
}
},
{
name: "email",
label: "이메일",
type: "text",
required: {
value: true,
message: "필수 입력 항목입니다."
},
placeholder: "이메일 주소를 입력해 주세요.",
regex: {
value: (/^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/g),
message: "올바른 값을 입력해주세요."
},
},
{
name: "url",
label: "관련 URL",
type: "text",
placeholder: "URL을 입력하세요."
},
{
name: "message",
label: "기타 메시지",
type: "textarea",
placeholder: "기타 메시지를 입력해 주세요.",
regex: {
value: (/^(?=.*[ㄱ-ㅎㅏ-ㅣ가-힣a-zA-Z0-9!@#$%^&*])(?=.{1,200}$)/),
message: "올바른 값을 입력해주세요."
},
maxLength: {
value: 200,
message: "최대 200자 이내로 작성할 수 있습니다."
}
},
{
name: "attachment",
label: "Upload",
type: "attachment"
}
];
- yup 적용 후
컴포넌트에 필요한 ui 정보와 유효성 검사 관련 코드를 분리하여 관리
// 스키마 정의
const schema = yup.object().shape({
name: yup
.string()
.trim()
.required('필수 입력 항목입니다.')
.max(30, '최대 30자 이내로 작성할 수 있습니다.')
.matches(/^[a-zA-Zㄱ-ㅎㅏ-ㅣ가-힣]{1,30}$/, '한글, 영어만 입력할 수 있습니다.'),
phoneNumber: yup
.string()
.trim()
.test(
"empty-or-validation",
'숫자만 입력할 수 있습니다.' as yup.Message<any>,
(value) => !value || (/^\d{1,11}$/g).test(value)
)
.max(11, '최대 11자 이내로 작성할 수 있습니다.'),
email: yup
.string()
.trim()
.required('필수 입력 항목입니다.')
.max(30, '최대 30자 이내로 작성할 수 있습니다.')
.email('올바른 값을 입력해주세요.')
url: yup
.string()
.url('올바른 url 주소를 입력해주세요.'),
message: yup
.string()
.trim()
.max(200, '기타 의견은 최대 200자 이내로 작성할 수 있습니다.')
.test(
"empty-or-validation",
'올바른 값을 입력해주세요.' as yup.Message<any>,
(value) => !value || (/^(?=.*[ㄱ-ㅎㅏ-ㅣ가-힣a-zA-Z0-9!@#$%^&*])(?=.{1,200}$)/).test(value)
)
});
// react-hook-form와 yup 연결
const {
register,
handleSubmit,
formState: { errors }
} = useForm<any>({ resolver: yupResolver(schema), mode: "onChange" });
const formArrayData = {
name: {
label: "이름",
type: "text",
placeholder: "이름을 입력해 주세요.",
},
phoneNumber: {
label: "전화번호",
type: "text",
placeholder: "전화번호를 입력해 주세요.",
},
email: {
label: "이메일",
type: "text",
placeholder: "이메일 주소를 입력해 주세요.",
},
url: {
label: "관련 URL",
type: "text",
placeholder: "URL을 입력하세요."
},
message: {
label: "기타 메시지",
type: "textarea",
placeholder: "기타 메시지를 입력해 주세요.",
},
};
마치며
yup 라이브러리를 사용함으로써 사용자의 입력 요소를 서비스에 알맞게 입력하게끔 적절한 피드백을 전달하는 과정을 스키마 정의하면서, 개발자 측면에서 코드는 간결해지고, 한눈에 인지할 수 있어 유지보수 측면에서 더 좋은 코드를 만들 수 있겠다는 생각이 들었습니다.
부족한 부분, 잘못된 내용이 있다면 댓글로 알려 주시면 수정하겠습니다.
읽어주셔서 감사합니다.
참고문서