HTML5 및 검증을 사용하여 React에서 빠른 양식 만들기

만약 웹 응용 프로그램을 만들고 있다면, 대부분의 시간을 폼을 만들어야 한다.표는 창조성과 맞춤형 수요로 악명이 높다.
우리가 폼 처리를 간소화할 수 있는 방법 중 하나는 데이터와 함수로 폼을 표시하는 것이다.이런 표시를 통해 우리는 폼 요소를 만들고, 읽고, 업데이트할 수 있다.
먼저 양식에 참여하거나 사용할 수 있는 가능한 양식 요소 목록을 살펴보겠습니다.
  • 텍스트
  • 이메일
  • 날짜
  • 시간
  • 비밀번호
  • 번호
  • 파일
  • 숨기기
  • 전화
  • 확인란
  • 라디오
  • 웹 주소
  • 범위
  • 검색
  • 등(주, 이미지, 월, 리셋, 제출 등)
  • 이제 입력 중 하나text에 대한 JS 객체 표현을 작성해 보겠습니다.
    const fields = [
      {
        type: "text", // input type
        name: "fullName", // Form input name
        label: "Full Name", // Label for Input
        placeholder: "John Doe" // Placeholder
      }
    ]
    
    React에서 다음과 같은 입력 유형의 텍스트를 만듭니다.
    // Input.js
    import React from "react";
    
    export function Input({ field }) {
      const id = `input-id-${+Date.now()}-${Math.random()}`
      return (
        <div className="form-field">
          <label htmlFor={id}>{field.label}</label>
          <input
            id={id}
            type={field.type}
            name={field.name}
            placeholder={field.placeholder}
          />
        </div>
      )
    }
    

    🤔 But what about events? We need values from the Form!


    FormData API를 사용하여 양식에서 값을 수집합니다.

    🤨 But we still need events to validate the Values!


    제출을 방지하기 위해 HTML5의 원본 폼 검증을 사용할 것입니다.
    하지만 고급 검증이 필요하다면.값을 조작하고 변경하기 위해 프로세서를 추가한 다음 서버에 보낼 수 있습니다
    const fields = [
      {
        type: 'text', // input type
        name: 'fullName', // Form input name
        label: 'Full Name', // Label for Input
        placeholder: 'John Doe', // Placeholder
        required: true
      }
    ]
    
    위의 필드 정의를 통해 다른 입력을 만들 수 있습니다.그러나 다른 텍스트 기반 입력의 경우 다음과 같이 입력 어셈블리가 표시됩니다.
    // Input.js
    import React, {useRef} from 'react'
    import TextInputRenderer from './InputType/Text'
    
    const getRenderer = (type) => {
      switch(type.toLowerCase()) {
        case 'tel':
        case 'url':
        case 'text':
        case 'date':
        case 'time':
        case 'file':
        case 'week':
        case 'month':
        case 'image':
        case 'email':
        case 'color':
        case 'range':
        case 'number':
        case 'search':
        case 'password':
          return TextInputRenderer
        default: return 'div'
      }
    }
    
    const Input = ({ field = {} }) => {
      const inputRef = useRef(null)
      const Component = getRenderer(field.type)
    
      return (
        <div className="form-field">
          <Component
            {...field}
            ref={inputRef}
            onChange={() => console.log(inputRef.current)}
          />
        </div>
      )
    }
    
    export default Input
    
    // components/InputType/Text.js
    import React, {Fragment} from 'react'
    
    export default React.forwardRef((props, ref) => {
        const id = `input-id-${+Date.now()}-${Math.random()}`
        return (
        <Fragment>
          <label htmlFor={id}>{props.label}</label>
            <input id={id} {...props} ref={ref} />
          </Fragment>
        )
    })
    
    위 코드에서 우리는 텍스트의 입력을 바탕으로 추출할 것이다components/InputType/Text.js.Input 구성 요소는 필요한 참조 포인트 및 프로세서만 연결합니다.
    여기서 주의해야 할 것은 ReactforwardRefref를 도구로 서브어셈블리에 전달한다.
    계속하기 전에, 우리는 독특한 ID를 사용하여 논리를 생성하고 있음을 알 수 있다.우리는 이 논리를 별도의 효용 함수로 추출할 수 있다.
    // src/utils/get-id.js
    export default () => [
      'input',
      +Date.now(),
      Math.random()
    ].join('-')
    
    앞으로 우리는 건장한 UUID와 uuid 같은 라이브러리를 함께 사용할 수 있다
    이제 라디오, 선택, 체크 상자를 입력하면 다른 표시로 표시됩니다.

    확인란


    일반적으로 단수 체크 상자는 텍스트 입력으로 표시됩니다.복선상자 목록에 흔히 볼 수 있는 용례가 있을 수 있지만, 이것은 우리가 구성 요소를 바꾸어야 하는 부분이다
    import React, {Fragment} from 'react'
    import getId from '../../utils/get-id'
    
    export default React.forwardRef((props, ref) => {
      const id = getId()
      return (
        <Fragment>
          <label htmlFor={id}>{props.label}</label>
          {props.options ? (
            <span className="flex-col">
              {props.options.map(item => {
                const id = getId()
                return (
                  <span key={id}>
                    <input id={id} {...props} value={item.value} />
                    <label htmlFor={id}>{item.label}</label>
                  </span>
                )
              })}
            </span>
          ) : <input id={id} {...props} ref={ref} />}
        </Fragment>
      )
    })
    

    라디오


    무선조의 경우 모든 입력은 같은 값name을 가진다.그리고 필드 정의는 선택 단추 목록을 만들기 위해 선택 목록을 받아들여야 합니다.
    import React, {Fragment} from 'react'
    import getId from '../../utils/get-id'
    
    export default React.forwardRef(({options, label, ...props}, ref) => (
      <Fragment>
        <label>{label}</label>
        <span className="flex-col">
          {options.map(item => {
            const id = getId()
            return (
              <span key={id}>
                <input id={id} {...props} value={item.value} />
                <label htmlFor={id}>{item.label}</label>
              </span>
            )
          })}
        </span>
      </Fragment>
    ))
    

    고르다


    옵션이 있고 기본 렌더링에서는 다른 옵션을 선택합니다.따라서, 우리는 select를 위해 다른 구성 요소를 만들어야 한다.
    import React, {Fragment} from 'react'
    import getId from '../../utils/get-id'
    
    export default React.forwardRef(({options, ...props}, ref) => {
      const id = getId()
      return (
        <Fragment>
          <label htmlFor={id}>{props.label}</label>
          <select ref={ref} {...props}>
            {options.map(item => (
              <option key={item.value} value={item.value} selected={item.selected}>
                {item.label}
              </option>
            ))}
          </select>
        </Fragment>
      )
    })
    
    현재 우리의 for가 설정되어 있습니다. 폼에서 검증을 처리해야 합니다.
    유효성 검사(예:
  • required 필드
  • minmax
  • maxLengthminLength
  • pattern
  • email
  • url
  • 이를 위해, 우리는 필드 정의로 돌아가야 한다.입력 유형과 검증에 필요한 속성을 각각 추가해야 합니다.이 점을 고려하면 다음과 같은 조합이 있을 수 있다.
    export default [
      {
        type: 'text', // input type
        name: 'fullName', // Form input name
        label: 'Full Name', // Label for Input
        placeholder: 'John Doe', // Placeholder
        pattern: '[A-Za-z\\s]{1,}',
        required: true
      },
      {
        type: 'date', // input type
        name: 'dob', // Form input name
        label: 'Date of Birth', // Label for Input
        required: true
      },
      {
        type: 'number', // input type
        name: 'workCapacity', // Form input name
        label: 'Weekly Work Capacity', // Label for Input
        required: true,
        min: 10,
        max: 8*7, // 8 hrs per day for 7 days of week
        step: 4 // half day steps
      },
      {
        type: 'file', // input type
        name: 'profilePicture', // Form input name
        label: 'Profile Picture', // Label for Input
        required: true
      },
      {
        type: 'radio',
        name: 'gender',
        label: 'Gender',
        required: true,
        options: [
          {
            label: 'Male',
            value: 'M'
          }, {
            label: 'Female',
            value: 'F'
          }, {
            label: 'Other',
            value: 'O'
          }, {
            label: 'I\'d rather not specify',
            value: '-'
          },
        ]
      },
      {
        type: 'checkbox',
        name: 'foodChoices',
        label: 'Food Choices',
        options: [
          {
            label: 'Vegan',
            value: 'V+'
          }, {
            label: 'Vegetarian',
            value: 'V'
          }, {
            label: 'Non Vegetarian',
            value: 'N'
          }, {
            label: 'I\'d rather not specify',
            value: '-'
          },
        ]
      },
      {
        type: 'select',
        name: 'primaryLanguage',
        label: 'Primary Language',
        required: true,
        options: [
          {
            label: 'English (US)',
            value: 'en_US'
          }, {
            label: 'English (UK)',
            value: 'en_UK'
          }, {
            label: 'Deutsch',
            value: 'de_DE'
          }, {
            label: 'French',
            value: 'fr_FR'
          }
        ]
      },
      {
        type: 'email',
        name: 'email',
        label: 'Your Email',
        required: true
      },
      {
        type: 'tel',
        name: 'phoneNumber',
        label: 'Your Phone Number',
        required: false,
        pattern: '[+0-9]{8,12}'
      },
      {
        type: 'url',
        name: 'homepage',
        label: 'Your Website',
        required: false
      },
      {
        type: 'password',
        name: 'password',
        label: 'Your Password',
        required: true
      },
      {
        type: 'password',
        name: 'confirmPassword',
        label: 'Confirm Password',
        required: true
      },
      {
        type: 'checkbox',
        name: 'terms',
        label: '',
        required: true,
        options: [{
          value: 'yes',
          label: 'Terms and Conditions'
        }]
      }
    ]
    
    이것은 HTML 양식의 필드를 제공합니다.

    HTML5가 검증됨에 따라 폼이 완전히 채워질 때까지 오류가 발생합니다.


    비록 여전히 약간의 검증이 필요하지만.속성이 있는 HTML5 검증에서는 이 기능을 사용할 수 없습니다.
    이를 위해서는 HTML5 인증 API를 통해 사용자 정의 인증이 필요합니다.
    HTML5의 검증 API는 매우 복잡한 속성과 방법을 제공하여 HTML5의 검증 기능을 이용한다.
    우선 ValidationState 인터페이스입니다.ValidationState 인터페이스는 가져온 요소의 유효성 검사 속성과 관련된 부울 상태를 제공합니다.예를 들면 다음과 같습니다.
  • valueMissingrequired의 브리 답안입니다.
  • tooLongmaxLength
  • tooShortminLength
  • rangeOverflowmax
  • rangeUnderflowmin
  • patternMismatchpattern
  • stepMismatch 일치 또는 정제할 수 있는 값
  • step 값이 입력 유형과 다르면typeMismatchurl만 해당
  • email 유효한 값을 입력하고 모든 유효성 검사를 통과
  • valid 사용자 정의 오류가 설정된 경우
  • 또 다른 부분은 매우 구체적인 방법customErrorsetCustomValidity을 통해우리는 이러한 방법을 사용하여 사용자 정의 검증을 보고할 것이다.
    입력 필드의 경우
  • reportValidity 입력 요소
  • 에 사용자 정의 오류 설정
  • setCustomValidity 입력 요소
  • 에 검증 오류가 표시됩니다.
  • reportValidity 설정을 통해 오류가 발생하면 customError를 True로 설정합니다.
  • 상기 내용을 효력을 발생시키기 위해서 사용자 정의 검증을 도입합니다.위 필드 중 하나를 사용합니다setCustomValidity:
      ...
      {
        type: 'checkbox',
        name: 'foodChoices',
        label: 'Food Choices',
        options: [
          ...
        ],
    +    validations: [
    +      (value, name, allFormValues, form) => ([
    +        Boolean(allFormValues[name]),
    +        `Please select atleast one of ${name}`
    +      ]),
    +      (value, name, allFormValues, form) => ([
    +        ['V+', 'V', 'N', '-'].includes(value),
    +        `Please select only from the provided choices for ${name}`
    +      ])
    +    ]
        },
      ...
    
    우리는 일련의 검증기를 받아들일 키 foodChoices 를 도입했다.
    이 검증기는 유효 상태를 되돌려줍니다. 유효하지 않으면 오류가 표시됩니다.
    이 검증기의 매개 변수는 따를 것이다
  • 필드
  • 의 값
  • 필드 이름
  • 비교 및 사용할 표의 모든 값
  • 자신을 더욱 높은 차원에서 운영하게 하고 대부분의 시간을 필요로 하지 않는다
  • 상기 검증을 통해, 우리는 폼 구성 요소 중의 일부 함수를 사용하여 폼 제출을 처리해야 한다.
    오류를 보고할 때, 입력 구성 요소도 변경해야 합니다.또한 즉시 검사를 실행하고 필드가 유효하면 오류를 제거해야 합니다.
    먼저 확인란 렌더기의 변경 사항을 살펴보겠습니다.
    // src/components/InputType/Checkbox.js
    import React, {Fragment, useRef, useEffect} from 'react'
    import getId from '../../utils/get-id'
    
    export default React.forwardRef(({registerField, ...props}, ref) => {
      const refs = useRef([])
      refs.current = (props.options || []).map(item => useRef(null))
      useEffect(() => {
        registerField(props.name, props.options ? refs : ref)
      }, [registerField, props.name, props.options])
      const id = getId()
      return (
        <Fragment>
          <label htmlFor={id}>{props.label}</label>
          {props.options ? (
            <span className="flex-col">
              {props.options.map((item, index) => {
                const id = getId()
                return (
                  <span key={id}>
                    <input id={id} {...props} value={item.value} ref={refs.current[index]} />
                    <label htmlFor={id}>{item.label}</label>
                  </span>
                )
              })}
            </span>
          ) : <input id={id} {...props} ref={ref} />}
        </Fragment>
      )
    })
    
    확인란을 표시하는 입력 구성 요소는 다음과 같이 변경됩니다.
    // src/components/Input.js
    // ...
    // code above here is same as before for renderers
    const Input = ({
      field = {},
      onChange = () => {},
      registerField = () => {},
    }) => {
      const inputRef = useRef(null)
      const Component = getRenderer(field.type)
      return (
        <div className="form-field">
          <Component
            {...field}
            ref={inputRef}
            registerField={registerField}
            onChange={(...args) => onChange(field.name, ...args)}
          />
        </div>
      )
    }
    
    양식 구성 요소는 위의 변경 사항을 사용하여 수동으로 확인합니다.
    export default function Form() {
      const form = useRef(null)
      const inputWithError = useRef(null)
      const fieldRefs = useRef({})
    
      const registerField = (key, ref) => {
        fieldRefs.current = {...fieldRefs.current, [key]: ref}
      }
    
      const getField = (key) => {
        return (
          Array.isArray(fieldRefs.current[key].current)
            ? fieldRefs.current[key].current[0]
            : fieldRefs.current[key]
        ).current
      }
    
      const resetError = (errorFieldKey) => {
        if (errorFieldKey) {
          const field = getField(errorFieldKey)
          if (!field) {
            return
          }
          field.setCustomValidity('');
          field.reportValidity();
        }
      }
    
      const handleChange = (key, ...args) => {
        resetError(inputWithError.current)
      }
    
      const customValidations = FIELDS.reduce(
        (acc, field) => field?.validations
          ? {...acc, [field.name]: field.validations}
          : acc
        , {}
      )
    
      const onSubmit = (e) => {
        e.preventDefault()
        if (inputWithError.current) {
          resetError(inputWithError.current)
        }
    
        if (!form.current.checkValidity()) {
          return false;
        }
    
        const formData = serialize(new FormData(form.current))
    
        let error = null
        // Check for custom validations
        const isValid = Object.keys(customValidations).reduce((acc, key) => {
          const validations = customValidations[key]
          const validity = validations.reduce((prevResult, validatorFn) => {
            // short circuit the validations if previous one has failed
            if (!prevResult) {
              return false
            }
            // previous one was valid, let's check for current validator and return the result
            const [valid, err] = validatorFn(formData[key], key, formData, form.current)
            if (!valid) {
              error = err
            }
            return valid
          }, true)
    
          acc[key] = validity;
          return acc;
        }, {})
    
        if (Object.keys(isValid).length) {
          const errField = Object.keys(isValid)[0]
          inputWithError.current = errField
          const field = getField(errField)
          if (!field) {
            return
          }
          field.setCustomValidity(error);
          field.reportValidity();
        }
      }
    
      return (
        <form className="form" ref={form} onSubmit={onSubmit}>
          {FIELDS.map((field) => (
            <Input
              key={field.name}
              field={field}
              registerField={registerField}
              onChange={handleChange}
            />
          ))}
          <button type='submit'>Submit</button>
        </form>
      )
    }
    
    위의 폼 구성 요소에는 많은 내용이 있습니다. 코드 블록을 보면서 분해를 시도해 보겠습니다.
    ...
    const form = useRef(null)
    const inputWithError = useRef(null)
    const fieldRefs = useRef({})
    ...
    
    이 블록은 창의 렌더링 사이에 정보를 남기기 위해 참조를 생성하고 있습니다.가장 중요한 것은 validations입니다.
    이ref는 HTML5 입력 요소의 모든ref를 수집합니다. 예를 들어 Input,select,radio,checkbox 등입니다.
    fieldRefs는 잘못된 lat 필드를 유지합니다.
    ...
    const registerField = (key, ref) => {
      fieldRefs.current = {...fieldRefs.current, [key]: ref}
    }
    ...
    
    상기 함수는 입력 요소를 inputWithError 집합에 등록하기 위해 렌더기에 전달됩니다.
    ...
    const getField = (key) => {
      return (
        Array.isArray(fieldRefs.current[key].current)
          ? fieldRefs.current[key].current[0]
          : fieldRefs.current[key]
      ).current
    }
    ...
    
    현재 이 fieldRefs 함수는 필드 이름에 따라 필드를 검색하는 데 도움을 줄 것입니다.
    필드에 접근할 때 논리가 필요하기 때문에 이 함수가 필요합니다.이것이 바로 한 곳에서 현장 방문을 간소화하는 것이 가장 좋은 이유다.
    ...
    const resetError = (errorFieldKey) => {
      if (errorFieldKey) {
        const field = getField(errorFieldKey)
        if (!field) {
          return
        }
        field.setCustomValidity('');
        field.reportValidity();
      }
    }
    ...
    
    현재, 이 함수는 모든 필드의 오류를 쉽게 리셋할 수 있습니다.
    ...
    const handleChange = (key, ...args) => {
      resetError(inputWithError.current)
    }
    ...
    
    필드의 변경 사항에 반응하기 위해 이 함수를 렌더링기에 전달할 것입니다.현재 용례에서 우리가 필요로 하는 유일한 반응은 오류를 제거하는 것이다.
    ...
    const customValidations = FIELDS.reduce(
      (acc, field) => field?.validations
        ? {...acc, [field.name]: field.validations}
        : acc
      , {}
    )
    ...
    
    위의 블록은 실행할 사용자 정의 검증을 추적하기 위해 집합된 하위 집합을 준비할 것입니다.우리가 필요한 검증을 찾아야 할 때, 이 집합은submit 방법에서 매우 편리하다.
    다음은 서명된 양식 제출 처리 프로그램입니다.
    ...
    const onSubmit = (e) => {
      e.preventDefault()
      ...
    }
    ...
    
    이 제출 처리 프로그램에서, 우리는 폼 데이터가 유효한지 확인하기 위해 몇 가지 조작을 실행합니다.submit 함수의 내용을 봅시다.
    ...
    const onSubmit = (e) => {
      e.preventDefault()
      if (inputWithError.current) {
        resetError(inputWithError.current)
      }
    
      if (!form.current.checkValidity()) {
        return false;
      }
      ...
    }
    ...
    
    위의 코드 블록에는 두 가지가 있다.
    우선 getFieldref와 inputWithError.current 함수를 사용하여 사용자 정의 오류를 제거합니다.
    두 번째는 HTML5를 사용하여 API의 resetErrorref와 form 함수를 사용하여 표의 유효성을 검증하는 것이다
    ...
    const formData = serialize(new FormData(form.current))
    
    let error = null
    ...
    
    다음에, 우리는 폼 데이터를 하나의 대상으로 준비하고, 폼 요소의 이름을 키와 값으로 한다.우리는 checkValidity API와 FormData 함수를 빌려 실현할 것이다.serialize 함수는 다음과 같습니다.
    export default function serialize (formData) {
      const values = {};
      for (let [key, value] of formData.entries()) {
        if (values[key]) {
          if ( ! (values[key] instanceof Array) ) {
            values[key] = new Array(values[key]);
          }
          values[key].push(value);
        } else {
          values[key] = value;
        }
      }
      return values;
    }
    
    FormData에 대해서는 아래 게시물에서 설명했습니다.위의 serialize 기능도 위의 게시물에서 대여한 것입니다.
    FormData API: Handle Forms like Boss 😎 - Time to Hack
    데이터를 서열화한 후에 우리는 검증을 실행해야 한다.양식 제출 방법의 다음 코드 블록에서 수행됩니다.
    ...
    // Check for custom validations
    const isValid = Object.keys(customValidations).reduce((acc, key) => {
      const validations = customValidations[key]
      const validity = validations.reduce((prevResult, validatorFn) => {
        // short circuit the validations if previous one has failed
        if (!prevResult) {
          return false
        }
        // previous one was valid, let's check for current validator and return the result
        const [valid, err] = validatorFn(formData[key], key, formData, form.current)
        if (!valid) {
          error = err
        }
        return valid
      }, true)
    
      acc[key] = validity;
      return acc;
    }, {})
    ...
    
    상술한 기능은 빠른 실효 전략 아래 일한다.검증되지 않은 청크는 전체 청크의 결과를 무효화합니다.필드 이름과 오류 메시지를 표시하지 못했습니다.
    글꼴 커밋 기능의 마지막 부분은 다음과 같습니다.
    ...
    if (Object.keys(isValid).length) {
      const errField = Object.keys(isValid)[0]
      inputWithError.current = errField
      const field = getField(errField)
      if (!field) {
        return
      }
      field.setCustomValidity(error);
      field.reportValidity();
    }
    
    여기서 검증기 함수 검사를 통해 오류가 발생하면 사용자 정의 오류를 설정합니다.serialize 사용자 정의 오류를 설정하고 setCustomValidity 사용자에게 이 오류를 표시하는 데 사용
    이것이 바로 우리가 브라우저 API를 이용하여 React에서 간단하고 빠른 폼을 실현하는 방법이다.

    결론


    만약 우리가 모든 일을 해 보려고 한다면, 형식은 항상 고통스럽다.그러나 브라우저에는 원하는 양식 유효성 검사를 작성하는 데 도움이 되는 매우 강력한 API가 있습니다.
    HTML5를 사용하여 맞춤형 폼을 구축하고 맞춤형 검증을 하는 방법을 알고 있습니다.
    표에 대한 관심은 무엇입니까?우리 함께 이 문제를 해결하도록 노력합시다.
    댓글로 알려주세요.💬 또는 Twitter에서 및/또는
    만약 이 문장이 도움이 된다면 다른 사람과 공유해 주십시오🗣
    블로그에 가입하여 받은 편지함에서 새 게시물을 받습니다.

    크레디트

  • IconFinder, [2], [3]
  • 의 아이콘
    애초 2020년 12월 15일https://time2hack.com에 발표됐다.

    좋은 웹페이지 즐겨찾기