정규표현식으로 밸리데이션 체크시 전역 플래그 사용 주의

ggong·2022년 1월 12일
0

폼이 많은 페이지를 개발하다보면 formik, RHF 등의 폼 라이브러리와 같이 yup으로 밸리데이션을 처리하게 된다. yup에서 자체적으로 지원하는 .required()min() 같은 함수도 있지만, 나는 추가로 필요한 밸리데이션 조건들이 있어서 yup.addMethod를 이용해 커스텀한 로직을 사용했다. 그 과정에서 정규 표현식을 사용하는데 이번에 삽질한 부분이 있어 기록으로 남겨보려고 함!

한글 자음, 모음만 입력했을 경우를 판별해 에러를 노출해야 하는 케이스였다.
아래처럼 커스텀 함수를 생성했고, 정규표현식을 사용했다.

// 한글 자음, 모음만 있는지 체크
yup.addMethod<yup.StringSchema>(
  yup.string,
  'disallowConsonant',
  function (message = '자음/모음은 입력할 수 없습니다.') {
    return this.test({
      message,
      name: 'hasConsonant',
      test: (value) => {
        if (!value) {
          return true;
        }
        return !regConsonant.test(value);
      },
    });
  },
);

const regConsonant = /[ㄱ-ㅎㅏ-ㅣ]/g;

test()는 주어진 문자열이 정규 표현식을 만족하는지 판별하고, 결과값을 boolean으로 리턴한다.
그리고 조건을 판별하는 과정에서 사용했던 정규표현식에 전역 탐색 플래그(/g)를 사용했다.

그런데 적용해보니 onChange가 발생할 때, 동일한 로직에서 true/false가 왔다갔다 하면서 리턴되는 문제가 발생했다. onChange시에 위 함수가 호출되는데 에러가 있어야 할 값인데도 true를 리턴하고 있었다. 콘솔에 같은 값으로 찍어봐도 어떨땐 true, 어떨땐 false를 리턴하는게 너무 이상했다.

(...???)

알게된 것 - 전역 플래그와 test()

test()는 전역 탐색 플래그를 제공한 정규 표현식에서 여러 번 반복 호출하게 되면, 이전 일치 이후부터 탐색을 한다. 다시 말해 이전 호출에서 판별했던 lastIndex를 기억해두고, 다음 호출 시에 검색할 때는 lastIndex부터 계속 진행한다. lastIndex 속성은 매번 test()가 true를 반환할 때마다 바뀌고, test()가 false를 반환하면 lastIndex 속성이 0으로 초기화된다.

위에 캡쳐한 케이스를 다시 보자.

test()가 호출되기 전 lastIndex는 0이다.
첫번째 test()문에서 판별하는 값은 'ㄱ가나다'이고, 판별된 결과는 true이므로 true가 리턴된다. 그러고나면 lastIndex, 즉 마지막까지 판별된 위치는 'ㄱ'의 다음부터이므로 1이 된다. 그리고 두번째 test()문에서는 lastIndex부터, 즉 1번째 글자부터인 '가나다'라는 값을 판별하게 된다. 자음/모음만이 아니므로 결과는 false를 리턴하고, 결과가 false이면 lastIndex 속성은 0으로 초기화된다. 그러면 세번째 test() 값은 다시 true가 되고, lastIndex는 1로 업데이트되며 그 다음 결과값에서는 false를 리턴하게 된다.

실제로 변수에 대입한 정규표현식을 통해regConsonant.lastIndex 형태로 접근해 보면, 매 실행마다 lastIndex값이 바뀌는 것을 알 수 있다.

이렇게 같은 문자열을 계속해서 test() 메소드로 검사하게 되면 실제로 test()를 수행하는 값을 다르게 보기 때문에 true와 false가 마구 섞여 나올 수 있다.
이것은 exec()를 사용할 때도 동일하다. exec() 메서드는 주어진 문자열에서 일치 탐색을 수행한 결과를 배열 혹은 null로 반환한다.

알아놓고 나면 별거 아닌 버그지만,
무작위로 리턴값이 달라져서 뜬금없는 삽질을 하게 될 수도 있으니
밸리데이션 체크를 위해 정규표현식을 사용할 때는 불필요한 전역 플래그 사용을 조심할 것!



참고 )
MDN : https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test

profile
파닥파닥 FE 개발자의 기록용 블로그

0개의 댓글