• [yup] 유효성 검증 :: 마이구미
    알아두면 좋은 라이브러리 2022. 6. 5. 10:52
    반응형
    이 글은 yup 이라는 라이브러리를 다룬다.
    yup 은 유효성 검증 로직에 큰 도움을 줄 수 있다.
    조금이나마 도움을 줄 수 있는 라이브러리들을 소개하는 카테고리로 분류된 글이다.
    알아두면 좋은 라이브러리

     

     

    • yup 이란?
    • Custom validation(without API)
    • Custom validation(with API)
    • Error Message customization
    • Transform, parsing
    • Examples
    • With react-hook-form

     

    yup 이란?

    yup 라이브러리는 스키마 유효성 검증이 목적이다.

    실제로는 서버쪽(Node.js) 에서 더 많이 사용된다.

    React 기준으로는 react-hook-form 과 함께 사용하고 있다.

    하지만 그건 크게 중요하지 않다.

    유효성 검증이라는 측면에서는 클라이언트, 서버 상관없이 모두 지원해야하는 기능이다.

    클라이언트는 검증된 데이터를 보내야하고, 서버는 검증된 데이터를 응답해야한다.

    클라이언트는 검증된 데이터를 받아야하고, 서버는 검증된 데이터를 처리해야한다.

    결국은 모두 유효성 검증 측면은 필수적인 기능이다.

     

    yup 을 활용하는 예제를 확인해보자.

    예제는 사용자에 대한 데이터들이 존재한다.

    이를 위한 스키마는 name, age, email, website, createdOn 으로 구성되어있다고 가정해보자.

     

    • name - string 타입, 필수 값
    • age - number 타입, 필수 값, 양수, int 범위
    • email - string 타입, 이메일 형태
    • website - string 타입, null 허용
    • createdOn - date 타입, 기본값은 현재 날짜

     

    위의 경우 타입 체크, 필수 값 여부, 이메일 형태 여부, 기본값 셋팅 등이 있다.

    결과적으로 요구되는 3가지는 타입 체크, 유효성 검증, 데이터 가공으로 분류할 수 있다.

    순수하게 작성하면 다음과 같다.

     

    if (typeof name !== 'string') { return 'String 타입이 아닙니다.' }
    if (!name) { return '필수 값입니다.' }
    
    if (typeof age !== 'number') { return 'Number 타입이 아닙니다.' }
    if (!age) { return '필수 값입니다.' }
    if (!isPositive(age)) { return '양수만 허용됩니다.' }
    if (!Number.isInteger(age)) { return 'int 범위가 아닙니다.' }
    ....

     

    위처럼 일일이 전부 작성해야될 것이다.

    이 모든걸 yup 으로 표현하면 다음과 같다.

     

    let userSchema = object({
      name: string('String 타입이 아닙니다.').required('필수 값입니다.'),
      age: number('Number 타입이 아닙니다.').required('필수 값입니다.').positive('양수만 허용됩니다.').integer('int 범위가 아닙니다.'),
      email: string().email(),
      website: string().url().nullable(),
      createdOn: date().default(() => new Date()),
    });

     

    제공하는 API 에 대해서는 학습이 필요하겠지만, 크게 어려움이 없을 것이라고 느낄 것이다.

    코드는 눈으로만 봐도 무엇을 표현하는지 이해하기 쉽다.

    복잡하지 않고 단순하다는 점이 yup 의 장점 중 하나이다.

     

    Custom validation without API

    유효성 검증을 위해 제공해주는 API 들이 아닌, 직접 원하는 유효성 검증을 만들 수 있다.

     

    {
      name: string().test(
        'is-jimmy',
        '${path} is not Jimmy',
        (value, context) => value === 'jimmy',
      );
    }

     

    "is-jimmy" 라는 유효성 검증을 만들었다.

    조건은 값이 "jimmy" 가 아니면 검증은 실패된다.

     

    Custom validation with API

    위에서는 제공해주는 검증 API 가 아닌 직접 원하는 유효성 검증을 만들었다.

    더 나아가서 string().email(), string().required() 처럼 새로운 메소드를 추가할 수 있다.

    이건 addMethods 를 이용하면 된다.

     

    addMethod(number, 'priceMin', function (args) {
        const { min = 10000, message } = args || {}
        return this.test('priceMin', (value, { createError }) => {
          return (
            value >= min ||
            createError({
              message: `${min} 원보다 커야합니다.`
            })
          )
        })
      })

     

    타입스크립트를 사용한다면, *.d.ts 에도 추가해줘야 자동 추론이 가능하다.

     

    declare module 'yup' {
      interface StringSchema<
        TType extends Maybe<string> = string | undefined,
        TContext extends AnyObject = AnyObject,
        TOut extends TType = TType,
      > extends BaseSchema<TType, TContext, TOut> {
        priceMin(params?: PriceMinParams): StringSchema<TType, TContext>
      }
    }

     

    결과적으로 string.priceMin() 으로 사용하게 된다.

     

    Error Message Customization

    에러 메시지는 따로 지정하지 않으면 기본값으로 노출된다.

    이것을 원하는 형태로 수정할 수 있고, 더 나아가 다국어도 지원 가능하다.

     

    setLocale({
        mixed: {
          default: intl.formatMessage({ id: 'validation.default' }),
          required: intl.formatMessage({ id: 'validation.required' }),
        },
        string: {
          min: ({ min }) =>
            intl.formatMessage({ id: 'validation.min' }, { length: min }),
        },
        number: {
          min: ({ min }) =>
            intl.formatMessage({ id: 'validation.min' }, { length: min }),
        },
      })

     

    Transform, Parsing

    검증뿐만 아니라 데이터 가공도 가능하다.

    프론트쪽에서의 활용 예제 중 하나는 다음과 같다.

     

    quantity: yup
      .number()
      .transform((value) => (isNaN(value) ? undefined : value))
      .required(),

     

    <input type="number"> 에서 만약 입력을 하지 않는다면 number() 검증에서 실패를 하게 된다.

    이를 위해 값을 가공하는 모습을 볼 수 있다.

     

    그 밖의 활용 예제들

    배열 최소 길이 1, 최대 길이 2

    yup.array().min(1).max(2)

     

    Enum 값

    yup.mixed().oneOf(Object.values(EnumValues))

     

    readOnly 배열값

    const validOptions = ["pass", "fail", "any"] as const;
    const schema = yup.mixed().oneOf([...validOptions]);

     

    데이터에 따라 동적으로 변하는 유효성 검증

    {
        like: yup.boolean(),
        comment: yup.string().when('like', {
          is: true,
          then: (schema) => schema.notRequired(),
          otherwise: (schema) => schema.required(),
        }),
    }

     

    데이터에 따라 동적으로 변하는 유효성 검증2

    {
      reason: isEtc ? yup.string().required() : yup.string().nullable(),
    }

     

    객체 Object

    email: yup.object().shape({
      id: yup.string().required(),
      domain: yup.string().required(),
    }),

     

    with react-hook-form

    react-hook-form 라이브러리를 사용하고 있다면, yup 과 조합하여 사용하는 걸 추천한다.

    (https://react-hook-form.com/get-started#schemavalidation)

     

    const schema = yup.object({
      firstName: yup.string().required(),
      age: yup.number().positive().integer().required(),
    }).required();
    
    export default function App() {
      const { register, handleSubmit, formState:{ errors } } = useForm({
        resolver: yupResolver(schema)
      });
      const onSubmit = data => console.log(data);
    
      return (
        <form onSubmit={handleSubmit(onSubmit)}>
          <input {...register("firstName")} />
          <p>{errors.firstName?.message}</p>
            
          <input {...register("age")} />
          <p>{errors.age?.message}</p>
          
          <input type="submit" />
        </form>
      );
    }

     

    복잡한 작업없이 yup 으로 셋팅된 스키마만 react-hook-form 에 주입해주면 된다.

    yup 에서는 form 데이터의 유효성 검증을 관리하면서 react-hook-form 은 기존 방식 그대로 사용하면 된다.

    반응형

    댓글

Designed by Tistory.