WangShuXian6 / blog

FE-BLOG
https://wangshuxian6.github.io/blog/
MIT License
46 stars 10 forks source link

React Hook Form[进行中] #192

Open WangShuXian6 opened 5 months ago

WangShuXian6 commented 5 months ago

React Hook Form

https://react-hook-form.com/ https://github.com/react-hook-form/react-hook-form React Hooks用于表单状态管理和验证(Web + React Native)

npm install react-hook-form
import { useForm } from 'react-hook-form';

function App() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm();

  return (
    <form onSubmit={handleSubmit((data) => console.log(data))}>
      <input {...register('firstName')} />
      <input {...register('lastName', { required: true })} />
      {errors.lastName && <p>Last name is required.</p>}
      <input {...register('age', { pattern: /\d+/ })} />
      {errors.age && <p>Please enter number for age.</p>}
      <input type="submit" />
    </form>
  );
}

特征

https://github.com/CodAffection/Premium-React-Hook-Form-Course-with-Food-Delivery-App https://github.com/CodAffection/Premium-React-Hook-Form-Course-with-User-Registration-Form https://discord.gg/tue9U7SkP5

WangShuXian6 commented 5 months ago

2. 创建普通表单的问题

React Hook Form 2024年版完整开发指南

1. 让我们从头开始

https://github.com/CodAffection/Premium-React-Hook-Form-Course-with-Food-Delivery-App/tree/2.1

创建项目 食品配送表

https://vitejs.cn/vite3-cn/guide/

npm create vite@latest food-delivery-form
react
typescript

pnpm i

pnpm run dev

图片

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h + enter to show help

2. 使用普通方法创建React表单的困难

使用 bootstrap cdn

可以使用国内镜像 https://cdn.bootcdn.net/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css

index.html


<!DOCTYPE html>
<html lang="en" data-bs-theme="dark">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;700&display=swap"
rel="stylesheet"
/>
<link
href="https://cdn.bootcdn.net/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css"
rel="stylesheet"
/>
<!-- <link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-4bw+/aepP/YC94hEpVNVgiZdgIC5+VKNBQNGCHeKRQN+PtmoHDEXuppvnDJzQIu9"
crossorigin="anonymous" -->
/>
<title>Vite + React + TS</title>
</head>


`src/index.css`
```css
:root {
  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
  line-height: 1.5;
  font-weight: 400;

  font-synthesis: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  -webkit-text-size-adjust: 100%;
}

body {
  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif !important;
  margin: 0;
  display: flex;
  place-items: center;
  min-width: 320px;
  min-height: 100vh;
}

src/App.css

#root {
  max-width: 1280px;
  margin: 0 auto;
  padding: 2rem;
  text-align: center;
}

src/FoodDeliveryForm.tsx

import { ChangeEvent, SyntheticEvent, useState } from "react"

type FoodDeliveryFormType = {
  customerName: string
  mobile: string
}

type FoodDeliveryFormErrorType = {
  customerName: string
  mobile: string
}

export const FoodDeliveryForm = () => {
  const [values, setValues] = useState<FoodDeliveryFormType>({
    customerName: "",
    mobile: "",
  })
  const [errors, setErrors] = useState<FoodDeliveryFormErrorType>({
    customerName: "",
    mobile: "",
  })

  const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target
    setValues({ ...values, [name]: value })
  }

  const validateFormData = () => {
    let tempErrors: FoodDeliveryFormErrorType = {
      customerName: "",
      mobile: "",
    }
    if (values.customerName == "")
      tempErrors.customerName = "Customer name is required."
    if (values.mobile == "") tempErrors.mobile = "Mobile number is required."
    setErrors(tempErrors)

    return Object.values(tempErrors).every((x) => x == "")
  }

  const onSubmit = (e: SyntheticEvent<HTMLFormElement>) => {
    e.preventDefault()
    if (validateFormData()) console.log("form data", values)
    else console.log("form is invalid")
  }

  return (
    <form autoComplete="off" onSubmit={onSubmit}>
      <div className="form-floating mb-3">
        <input
          type="text"
          name="customerName"
          className="form-control"
          placeholder="Customer Name"
          value={values.customerName}
          onChange={handleInputChange}
        />
        <label>Customer Name</label>
      </div>
      <div className="form-floating mb-3">
        <input
          type="text"
          name="mobile"
          className="form-control"
          placeholder="Mobile"
          value={values.mobile}
          onChange={handleInputChange}
        />
        <label>Mobile</label>
      </div>
      <button type="submit" className="btn btn-primary">
        Submit
      </button>
    </form>
  )
}

src/App.tsx

import "./App.css"
import { FoodDeliveryForm } from "./FoodDeliveryForm"

function App() {
  return (
    <>
      <div className="container">
        <div className="mx-5">
          <FoodDeliveryForm />
        </div>
      </div>
    </>
  )
}

export default App

图片

3. 开始使用React Hook Form

pnpm i react-hook-form -S

传统表单组件重命名为 TypicalForm

src/TypicalForm.tsx


import { ChangeEvent, SyntheticEvent, useState } from "react"

type FoodDeliveryFormType = { customerName: string mobile: string }

type FoodDeliveryFormErrorType = { customerName: string mobile: string }

export const TypicalForm = () => { const [values, setValues] = useState({ customerName: "", mobile: "", }) const [errors, setErrors] = useState({ customerName: "", mobile: "", })

const handleInputChange = (e: ChangeEvent) => { const { name, value } = e.target setValues({ ...values, [name]: value }) }

const validateFormData = () => { let tempErrors: FoodDeliveryFormErrorType = { customerName: "", mobile: "", } if (values.customerName == "") tempErrors.customerName = "Customer name is required." if (values.mobile == "") tempErrors.mobile = "Mobile number is required." setErrors(tempErrors)

return Object.values(tempErrors).every((x) => x == "")

}

const onSubmit = (e: SyntheticEvent) => { e.preventDefault() if (validateFormData()) console.log("form data", values) else console.log("form is invalid") }

return (

) }


### 使用 react-hook-form 的高效表单组件

{...register("customerName", { required: "Customer name is required.", })}

替代传统的状态,事件,验证绑定,
这样,可以防止传统的多次输入重复渲染。简化代码。

>`src/FoodDeliveryForm.tsx`
```tsx
import { useForm } from "react-hook-form"

type FoodDeliveryFormType = {
  customerName: string
  mobile: string
}

export const FoodDeliveryForm = () => {
  const { register, handleSubmit } = useForm<FoodDeliveryFormType>()

  const onSubmit = (formData: FoodDeliveryFormType) => {
    console.log("form data", formData)
  }

  const onError = (errors) => {
    console.log("validation errors", errors)
  }

  return (
    <form autoComplete="off" onSubmit={handleSubmit(onSubmit, onError)}>
      <div className="form-floating mb-3">
        <input
          type="text"
          className="form-control"
          placeholder="Customer Name"
          {...register("customerName", {
            required: "Customer name is required.",
          })}
        />
        <label>Customer Name</label>
      </div>
      <div className="form-floating mb-3">
        <input
          type="text"
          className="form-control"
          placeholder="Mobile"
          {...register("mobile", {
            required: "Mobile number is required.",
          })}
        />
        <label>Mobile</label>
      </div>
      <button type="submit" className="btn btn-primary">
        Submit
      </button>
    </form>
  )
}