회원가입 유효성 검사

Jieunmoon·2023년 11월 14일

UI/UX적 고민 🤔

맞춤법 검사를 원하는 단어 프로젝트를 진행하면서 고민했던 부분은 회원가입 폼에서 인풋에 값이 입력될 때 즉 handleChange에 유효성 검사를 해주어 입력할 때마다 검사를 해줄 것인지 혹은 값이 다 입력되고 입력 필드에서 포커스가 빠져나갈 때 유효성 검사를 해줄 것인지?

Refactor 전

처음에는 회원가입 유효성 검사는 인풋에 값을 입력 받을 때 마다 했다.

하지만 사용자에게 아직 인풋이 다 입력되지 않았는데, 입력할 때마다 보이는 빨간 테두리와 에러 메시지는 사용자로 하여금 좋은 경험이 아니라고 생각된다.

Refactor 후 ✨

handleBlur를 사용하여사용자가 해당 필드에서 포커스를 떼었을 때 입력값의 유효성을 검사하여 에러 메시지를 표시하게 변경하였습니다.

🙆‍♀️ 올바른 데이터를 받기 위한 유효성 검사를 적용하였습니다.

리팩토링 전 코드에는 반복되는 switch-case 구조로 인하여 가독성이 떨어지는 문제가 있습니다.

Refactor 전 코드

const validateField = (fieldName: keyof FormData, value: string) => {
    let error = "";
  
    switch (fieldName) {
      case "userName":
        if (!value) {
          error = "";
        } else if (value.length === 1 && /^[ㄱ-ㅎㅏ-ㅣ]+$/.test(value)) {
          error = "올바르지 않은 이름 형식입니다.";
        } else if (/[ㄱ-ㅎㅏ-ㅣ0-9!@#$%^&*(),.?":{}|<>]/.test(value)) {
          error = "특수문자나 숫자, 초성은 사용할 수 없습니다.";
        }
        break;
      case "phoneNumber":
        if (!value) {
          error = "";
        } else if (!/^\d+$/.test(value) || value.length !== 11) {
          error = "- 없이 11자리의 숫자를 입력해주세요.";
        }
        break;
      case "email":
        if (!value) {
          error = "";
        } else if (
          !/^[A-Za-z0-9]([-_.]?[A-Za-z0-9_])*@[A-Za-z0-9]([-_.]?[A-Za-z0-9])*.[A-Za-z]{2,3}$/.test(
            value
          )
        ) {
          error = "올바른 이메일 형식이 아닙니다.";
        }
        break;
      case "password":
        if (!value) {
          error = "";
        } else if (
          !/(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,16}/.test(
            value
          )
        ) {
          error =
            "영문 대소문자와 숫자, 특수문자 중 2가지 이상 조합하여 8~16자여야 합니다.";
        }
        break;
      case "checkPassword":
        if (!value) {
          error = "";
        } else if (value !== formData.password) {
          error = "비밀번호가 일치하지 않습니다.";
        }
        break;
      case "detailAddress":
        if (!value.trim()) {
          error = "상세주소를 입력해주세요";
        }
        break;
      default:
        break;
    }
    setValidationErrors((prevValidationErrors) => ({
      ...prevValidationErrors,
      [fieldName]: error,
    }));
  };

변경사항

코드 가독성을 향상시키기 위해 변경하였습니다.

  • 에러메세지 판단 함수 생성
    • 유효성 검사를 하여 검사에 통과하는 것은 true,
      부합하는 것은 false로 boolean 값을 뱉고,
      true경우 ""return
      유효성 검사 실패시(false) errorMessage를 반환하는 함수
  • switch문 제거

리팩토링을 하면서 새롭게 써본 타입에 대해 간단하게 정리해보았다 💻

Record타입 사용

  • TypeScript에서 기본적으로 제공되는 유틸리티 타입 중 하나로 객체의 키-값 쌍에 대한 타입을 정의하는데 사용한다.

아래와 같은 예시에서

  • "키"는 객체의 프로퍼티 이름userName이며,
    () => {...}는 그 키에 해당하는 함수입니다.

💬 프로젝트 코드 예시

const validationRules: Record<string, () => string> = {
      userName: () => {
        if (!value) return "";
        if (value.length === 1 && /^[ㄱ-ㅎㅏ-ㅣ]+$/.test(value)) {
          return "올바르지 않은 이름 형식입니다.";
        }
        if (/[ㄱ-ㅎㅏ-ㅣ0-9!@#$%^&*(),.?":{}|<>]/.test(value)) {
          return "특수문자나 숫자, 초성은 사용할 수 없습니다.";
        }
        return "";
      },
      phoneNumber: () => {
        if (!value) return "";
        return getErrorMessage(
          () => /^\d+$/.test(value) && value.length === 11,
          "- 없이 11자리의 숫자를 입력해주세요."
        );
      },

Refactor 후 코드✨

 const validateField = (fieldName: keyof FormData, value: string) => {
    const getErrorMessage = (
      rule: (value: string) => boolean,
      errorMessage: string
    ) => {
      return rule(value) ? "" : errorMessage;
    };

    const validationRules: Record<string, () => string> = {
      userName: () => {
        if (!value) return "";
        if (value.length === 1 && /^[ㄱ-ㅎㅏ-ㅣ]+$/.test(value)) {
          return "올바르지 않은 이름 형식입니다.";
        }
        if (/[ㄱ-ㅎㅏ-ㅣ0-9!@#$%^&*(),.?":{}|<>]/.test(value)) {
          return "특수문자나 숫자, 초성은 사용할 수 없습니다.";
        }
        return "";
      },
      phoneNumber: () => {
        if (!value) return "";
        return getErrorMessage(
          () => /^\d+$/.test(value) && value.length === 11,
          "- 없이 11자리의 숫자를 입력해주세요."
        );
      },
      email: () => {
        if (!value) return "";
        return getErrorMessage(
          () =>
            /^[A-Za-z0-9]([-_.]?[A-Za-z0-9_])*@[A-Za-z0-9]([-_.]?[A-Za-z0-9])*.[A-Za-z]{2,3}$/.test(
              value
            ),
          "올바른 이메일 형식이 아닙니다."
        );
      },
      password: () => {
        if (!value) return "";
        return getErrorMessage(
          () =>
            /(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,16}/.test(
              value
            ),
          "영문 대소문자와 숫자, 특수문자 중 2가지 이상 조합하여 8~16자여야 합니다."
        );
      },
      checkPassword: () => {
        if (!value) return "";
        return getErrorMessage(
          () => value === formData.password,
          "비밀번호가 일치하지 않습니다."
        );
      },
      detailAddress: () => {
        return !value.trim() ? "상세주소를 입력해주세요" : "";
      },
    };
    const error = validationRules[fieldName]();
    setValidationErrors((prevValidationErrors) => ({
      ...prevValidationErrors,
      [fieldName]: error,
    }));
  };

1개의 댓글

comment-user-thumbnail
2023년 11월 14일

유익한 자료 감사합니다.

답글 달기