폼이 많은 페이지를 개발하다보면 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