나만의 React 유효성 검사 라이브러리 만들기: 기본 사항(1부)
게시 됨: 2022-03-10나는 항상 양식 유효성 검사 라이브러리가 꽤 멋지다고 생각했습니다. 나는 그것이 틈새 시장이라는 것을 압니다. 하지만 우리는 그것들을 너무 많이 사용합니다! 적어도 내 직업에서 - 내가 하는 대부분은 초기 선택과 경로에 의존하는 유효성 검사 규칙을 사용하여 다소 복잡한 양식을 구성하는 것입니다. 양식 유효성 검사 라이브러리가 작동하는 방식을 이해하는 것이 가장 중요합니다.
작년에 저는 그러한 양식 유효성 검사 라이브러리 중 하나를 작성했습니다. 나는 그것을 "캘리데이션"이라고 명명했으며 여기에서 소개 블로그 게시물을 읽을 수 있습니다. 그것은 많은 유연성을 제공하고 시장의 다른 것들과 약간 다른 접근 방식을 사용하는 좋은 라이브러리입니다. 하지만 다른 훌륭한 라이브러리도 많이 있습니다. 제 요구 사항 에 맞게 잘 작동했습니다.
오늘은 React용 검증 라이브러리 를 작성하는 방법을 보여드리겠습니다. 프로세스를 단계별로 진행하고 진행하면서 CodeSandbox 예제를 찾을 수 있습니다. 이 기사가 끝나면 자신의 유효성 검사 라이브러리를 작성하는 방법을 알게 되거나 최소한 다른 라이브러리가 "마법의 유효성 검사"를 구현하는 방법에 대해 더 깊이 이해하게 될 것입니다.
- 1부: 기본 사항
- 2부: 기능
- 3부: 경험
1단계: API 설계
라이브러리를 만드는 첫 번째 단계는 라이브러리를 어떻게 사용할 것인지 설계하는 것입니다. 그것은 앞으로 나올 많은 작업의 기초가 되며, 제 생각에는 이것이 당신이 도서관에서 내리게 될 가장 중요한 결정입니다.
"사용하기 쉬우면서도" 향후 개선 및 고급 사용 사례를 허용할 만큼 충분히 유연한 API를 만드는 것이 중요합니다. 우리는 이 두 가지 목표를 모두 달성하기 위해 노력할 것입니다.
단일 구성 개체를 허용하는 사용자 지정 후크를 만들 것입니다. 이렇게 하면 주요 변경 사항을 도입하지 않고 향후 옵션을 전달할 수 있습니다.
후크에 대한 참고 사항
Hooks는 React를 작성하는 꽤 새로운 방법입니다. 과거에 React를 작성했다면 이러한 개념 중 일부를 인식하지 못할 수도 있습니다. 이 경우 공식 문서를 살펴보십시오. 그것은 믿을 수 없을 정도로 잘 작성되었으며 알아야 할 기본 사항을 안내합니다.
지금은 사용자 정의 후크를 useValidation
이라고 부를 것입니다. 사용법은 다음과 같습니다.
const config = { fields: { username: { isRequired: { message: 'Please fill out a username' }, }, password: { isRequired: { message: 'Please fill out a password' }, isMinLength: { value: 6, message: 'Please make it more secure' } } }, onSubmit: e => { /* handle submit */ } }; const { getFieldProps, getFormProps, errors } = useValidation(config);
config
개체는 각 필드에 대한 유효성 검사 규칙을 설정하는 fields
prop을 수락합니다. 또한 양식이 제출될 때 콜백을 수락합니다.
fields
객체에는 유효성을 검사하려는 각 필드에 대한 키가 포함되어 있습니다. 각 필드에는 고유한 구성이 있으며, 여기서 각 키는 유효성 검사기 이름이고 각 값은 해당 유효성 검사기의 구성 속성입니다. 같은 것을 작성하는 또 다른 방법은 다음과 같습니다.
{ fields: { fieldName: { oneValidator: { validatorRule: 'validator value' }, anotherValidator: { errorMessage: 'something is not as it should' } } } }
useValidation
후크는 getFieldProps
, getFormProps
및 errors
와 같은 몇 가지 속성이 있는 객체를 반환합니다. 두 개의 첫 번째 함수는 Kent C. Dodds가 "prop getters"라고 부르는 것이며(여기에 대한 훌륭한 기사 참조) 지정된 양식 필드 또는 양식 태그에 대한 관련 소품을 가져오는 데 사용됩니다. errors
소품은 필드별로 키가 지정된 오류 메시지가 있는 개체입니다.
이 사용법은 다음과 같습니다.
const config = { ... }; // like above const LoginForm = props => { const { getFieldProps, getFormProps, errors } = useValidation(config); return ( <form {...getFormProps()}> <label> Username<br/> <input {...getFieldProps('username')} /> {errors.username && <div className="error">{errors.username}</div>} </label> <label> Password<br/> <input {...getFieldProps('password')} /> {errors.password && <div className="error">{errors.password}</div>} </label> <button type="submit">Submit my form</button> </form> ); };
알았어! 그래서 우리는 API를 못 박았습니다.
- CodeSandbox 데모 보기
useValidation
후크의 모의 구현도 만들었습니다. 지금은 필요한 객체와 기능이 있는 객체를 반환할 뿐이므로 샘플 구현을 중단하지 않습니다.
양식 상태 저장
가장 먼저 해야 할 일은 사용자 정의 후크에 모든 양식 상태를 저장하는 것입니다. 각 필드의 값, 오류 메시지 및 양식이 제출되었는지 여부를 기억해야 합니다. 우리는 이것을 위해 useReducer
후크를 사용할 것입니다. 왜냐하면 그것이 가장 유연하고 상용구가 적기 때문입니다. Redux를 사용해 본 적이 있다면 몇 가지 친숙한 개념을 보게 될 것입니다. 그렇지 않은 경우 진행하면서 설명하겠습니다! useReducer
후크에 전달되는 감속기를 작성하여 시작하겠습니다.
const initialState = { values: {}, errors: {}, submitted: false, }; function validationReducer(state, action) { switch(action.type) { case 'change': const values = { ...state.values, ...action.payload }; return { ...state, values, }; case 'submit': return { ...state, submitted: true }; default: throw new Error('Unknown action type'); } }
감속기 란 무엇입니까?
리듀서는 값 객체와 "액션"을 받아들이고 값 객체의 증강 버전을 반환하는 함수입니다.
작업은 type
속성이 있는 일반 JavaScript 개체입니다. 가능한 각 작업 유형을 처리하기 위해 switch
문을 사용하고 있습니다.
"가치의 개체"는 종종 상태 라고 하며, 우리의 경우 유효성 검사 논리의 상태입니다.
우리의 상태는 values
(양식 필드의 현재 값), errors
(현재 오류 메시지 세트) 및 양식이 한 번 이상 제출되었는지 여부를 나타내는 isSubmitted
플래그의 세 가지 데이터로 구성됩니다.
양식 상태를 저장하려면 useValidation
후크의 몇 가지 부분을 구현해야 합니다. getFieldProps
메소드를 호출할 때 해당 필드의 값, 변경 시점에 대한 변경 핸들러 및 어떤 필드가 어느 것인지 추적하기 위한 name prop이 있는 객체를 반환해야 합니다.
function validationReducer(state, action) { // Like above } const initialState = { /* like above */ }; const useValidation = config => { const [state, dispatch] = useReducer(validationReducer, initialState); return { errors: state.errors, getFormProps: e => {}, getFieldProps: fieldName => ({ onChange: e => { if (!config.fields[fieldName]) { return; } dispatch({ type: 'change', payload: { [fieldName]: e.target.value } }); }, name: fieldName, value: state.values[fieldName], }), }; };
getFieldProps
메서드는 이제 각 필드에 필요한 소품을 반환합니다. 변경 이벤트가 발생하면 필드가 유효성 검사 구성에 있는지 확인한 다음 감속기에 change
작업이 발생했음을 알립니다. 감속기는 유효성 검사 상태의 변경 사항을 처리합니다.
- CodeSandbox 데모 보기
양식 확인
우리의 양식 유효성 검사 라이브러리는 좋아 보이지만 양식 값의 유효성을 검사하는 데는 별로 도움이 되지 않습니다! 수정합시다.
모든 변경 이벤트에서 모든 필드의 유효성을 검사할 것입니다. 이것은 그다지 효율적이지 않은 것처럼 들릴 수 있지만 내가 만난 실제 응용 프로그램에서는 실제로 문제가 되지 않습니다.
모든 변경 사항에 대해 모든 오류를 표시해야 한다는 것은 아닙니다. 이 문서 뒷부분에서 필드를 제출하거나 다른 곳으로 이동할 때만 오류를 표시하는 방법을 다시 살펴보겠습니다.
유효성 검사기 기능을 선택하는 방법
유효성 검사기와 관련하여 필요한 모든 유효성 검사 방법을 구현하는 수많은 라이브러리가 있습니다. 원하는 경우 직접 작성할 수도 있습니다. 재미있는 운동입니다!
이 프로젝트에서 우리는 내가 얼마 전에 작성한 일련의 유효성 검사기인 calidators
를 사용할 것입니다. 이러한 유효성 검사기에는 다음 API가 있습니다.
function isRequired(config) { return function(value) { if (value === '') { return config.message; } else { return null; } }; } // or the same, but terser const isRequired = config => value => value === '' ? config.message : null;
즉, 각 유효성 검사기는 구성 개체를 수락하고 완전히 구성된 유효성 검사기를 반환합니다. 해당 함수가 값과 함께 호출되면 값이 유효하지 않으면 message
prop을 반환하고 유효하면 null
을 반환합니다. 소스 코드를 보면 이러한 유효성 검사기 중 일부가 어떻게 구현되는지 확인할 수 있습니다.
이러한 유효성 검사기에 액세스하려면 npm install calidators
와 함께 calidators
패키지를 설치하십시오.
단일 필드 유효성 검사
useValidation
객체에 전달한 구성을 기억하십니까? 다음과 같이 보입니다.
{ fields: { username: { isRequired: { message: 'Please fill out a username' }, }, password: { isRequired: { message: 'Please fill out a password' }, isMinLength: { value: 6, message: 'Please make it more secure' } } }, // more stuff }
구현을 단순화하기 위해 유효성을 검사할 단일 필드만 있다고 가정하겠습니다. 필드 구성 개체의 각 키를 살펴보고 오류를 찾거나 유효성 검사가 완료될 때까지 유효성 검사기를 하나씩 실행합니다.
import * as validators from 'calidators'; function validateField(fieldValue = '', fieldConfig) { for (let validatorName in fieldConfig) { const validatorConfig = fieldConfig[validatorName]; const validator = validators[validatorName]; const configuredValidator = validator(validatorConfig); const errorMessage = configuredValidator(fieldValue); if (errorMessage) { return errorMessage; } } return null; }
여기에서 유효성을 검사할 값과 해당 필드에 대한 유효성 검사기 구성을 허용하는 validateField
함수를 작성했습니다. 모든 유효성 검사기를 반복하고 해당 유효성 검사기에 대한 구성을 전달하고 실행합니다. 오류 메시지가 나타나면 나머지 유효성 검사기를 건너뛰고 반환합니다. 그렇지 않은 경우 다음 유효성 검사기를 시도합니다.
참고: 유효성 검사기 API에서
다른 API를 사용하는 다른 유효성 검사기를 선택하면(예: 매우 인기 있는 validator.js
) 코드의 이 부분이 약간 다르게 보일 수 있습니다. 그러나 간결함을 위해 그 부분은 독자에게 맡겨진 연습 문제로 두었습니다.
참고: on for…in 루프
전에 for...in
루프를 사용한 적이 없습니까? 괜찮아, 나도 처음이야! 기본적으로 객체의 키를 반복합니다. MDN에서 더 많은 정보를 읽을 수 있습니다.
모든 필드 검증
이제 한 필드의 유효성을 검사했으므로 큰 문제 없이 모든 필드의 유효성을 검사할 수 있습니다.
function validateField(fieldValue = '', fieldConfig) { // as before } function validateFields(fieldValues, fieldConfigs) { const errors = {}; for (let fieldName in fieldConfigs) { const fieldConfig = fieldConfigs[fieldName]; const fieldValue = fieldValues[fieldName]; errors[fieldName] = validateField(fieldValue, fieldConfig); } return errors; }
모든 필드 값과 전체 필드 구성을 허용하는 함수 validateFields
를 작성했습니다. 구성의 각 필드 이름을 반복하고 구성 개체 및 값으로 해당 필드의 유효성을 검사합니다.
다음: 감속기에 알리기
자, 이제 우리는 모든 것을 검증하는 이 함수를 갖게 되었습니다. 코드의 나머지 부분에 적용해 봅시다!
먼저 validationReducer
에 validate
작업 처리기를 추가합니다.
function validationReducer(state, action) { switch (action.type) { case 'change': // as before case 'submit': // as before case 'validate': return { ...state, errors: action.payload }; default: throw new Error('Unknown action type'); } }
validate
작업을 트리거할 때마다 상태의 오류를 작업과 함께 전달된 오류로 바꿉니다.
다음으로 useEffect
후크에서 유효성 검사 논리를 트리거합니다.
const useValidation = config => { const [state, dispatch] = useReducer(validationReducer, initialState); useEffect(() => { const errors = validateFields(state.fields, config.fields); dispatch({ type: 'validate', payload: errors }); }, [state.fields, config.fields]); return { // as before }; };
이 useEffect
후크는 state.fields
또는 config.fields
가 변경될 때마다 실행되며 첫 번째 마운트 시에도 실행됩니다.
버그 조심
위의 코드에는 아주 미묘한 버그가 있습니다. state.fields
또는 config.fields
가 변경될 때마다 useEffect
후크가 다시 실행되도록 지정했습니다. "변화"가 반드시 가치의 변화를 의미하지는 않습니다! useEffect
는 Object.is
를 사용하여 객체 간의 평등을 보장하고, 차례로 참조 평등을 사용합니다. 즉, 동일한 내용을 가진 새 객체를 전달하면 객체 자체가 새 것이기 때문에 동일하지 않습니다.
state.fields
는 이 참조 동등성을 보장하는 useReducer
에서 반환되지만 config
은 함수 구성 요소에 인라인으로 지정됩니다. 즉, 모든 렌더에서 개체가 다시 만들어지고 위의 useEffect
가 트리거됩니다!
이를 해결하려면 Kent C. Dodds의 use-deep-compare-effect
라이브러리를 사용해야 합니다. npm install use-deep-compare-effect
를 사용하여 설치하고 useEffect
호출을 대신 사용합니다. 이것은 참조 동등성 검사 대신 깊은 동등성 검사를 수행하도록 합니다.
이제 코드가 다음과 같이 표시됩니다.
import useDeepCompareEffect from 'use-deep-compare-effect'; const useValidation = config => { const [state, dispatch] = useReducer(validationReducer, initialState); useDeepCompareEffect(() => { const errors = validateFields(state.fields, config.fields); dispatch({ type: 'validate', payload: errors }); }, [state.fields, config.fields]); return { // as before }; };
useEffect에 대한 참고 사항
useEffect
는 꽤 흥미로운 기능입니다. Dan Abramov는 이 후크에 대한 모든 것을 배우고 useEffect
의 복잡함에 대해 정말 훌륭하고 긴 기사를 썼습니다.
이제 유효성 검사 라이브러리처럼 보이기 시작했습니다!
- CodeSandbox 데모 보기
양식 제출 처리
기본 양식 유효성 검사 라이브러리의 마지막 부분은 양식을 제출할 때 발생하는 일을 처리하는 것입니다. 지금은 페이지를 새로고침하고 아무 일도 일어나지 않습니다. 그것은 최적이 아닙니다. 우리는 양식과 관련하여 기본 브라우저 동작을 방지하고 대신 직접 처리하기를 원합니다. 이 로직을 getFormProps
prop getter 함수 안에 넣습니다.
const useValidation = config => { const [state, dispatch] = useReducer(validationReducer, initialState); // as before return { getFormProps: () => ({ onSubmit: e => { e.preventDefault(); dispatch({ type: 'submit' }); if (config.onSubmit) { config.onSubmit(state); } }, }), // as before }; };
submit
DOM 이벤트가 트리거될 때마다 트리거되는 onSubmit
함수를 반환하도록 getFormProps
함수를 변경합니다. 우리는 기본 브라우저 동작을 방지하고, 우리가 제출한 리듀서에게 액션을 전달하고, 제공된 경우 전체 상태와 함께 제공된 onSubmit
콜백을 호출합니다.
요약
있었다! 우리는 간단하고 유용하며 멋진 유효성 검사 라이브러리를 만들었습니다. 하지만 우리가 인터웹을 지배할 수 있으려면 아직 해야 할 일이 많습니다.
- 1부: 기본 사항
- 2부: 기능
- 3부: 경험