React使用formik+yup创建表单

React使用formik+yup创建表单

简述

React中,react提供了ref来让我们获取react元素对应的真实的dom元素,利用这个特性我们确实可以获取表单域所对应的值

import React, { Fragment, useRef, useState } from "react";

export default function A() {
  const inputRef = useRef<any>();

  const [value, setValue] = useState();

  const onSubmit = () => {
    setValue(inputRef?.current?.value);
  };

  return (
    <Fragment>
      <input type="text" ref={inputRef} />
      <button onClick={onSubmit}>提交</button>
      <p>输入的值为: {value}</p>
    </Fragment>
  );
}

但是显然这中方式在实战中的庞大表单结构中会显得非常无力和麻烦。

ant design解决方案

如果使用UI框架Ant Design开发前端应用那么我们可以使用Antd自带的表单体系生成和验证表单:

import { Form, Input, Button, Checkbox } from 'antd';

const Demo = () => {
  const onFinish = (values) => {
    console.log('Success:', values);
  };

  const onFinishFailed = (errorInfo) => {
    console.log('Failed:', errorInfo);
  };

  return (
    <Form
      name="basic"
      labelCol={{
        span: 8,
      }}
      wrapperCol={{
        span: 16,
      }}
      initialValues={{
        remember: true,
      }}
      onFinish={onFinish}
      onFinishFailed={onFinishFailed}
      autoComplete="off"
    >
      <Form.Item
        label="Username"
        name="username"
        rules={[
          {
            required: true,
            message: 'Please input your username!',
          },
        ]}
      >
        <Input />
      </Form.Item>

      <Form.Item
        label="Password"
        name="password"
        rules={[
          {
            required: true,
            message: 'Please input your password!',
          },
        ]}
      >
        <Input.Password />
      </Form.Item>

      <Form.Item
        name="remember"
        valuePropName="checked"
        wrapperCol={{
          offset: 8,
          span: 16,
        }}
      >
        <Checkbox>Remember me</Checkbox>
      </Form.Item>

      <Form.Item
        wrapperCol={{
          offset: 8,
          span: 16,
        }}
      >
        <Button type="primary" htmlType="submit">
          Submit
        </Button>
      </Form.Item>
    </Form>
  );
};

ReactDOM.render(<Demo />, mountNode);

formik+yup方案

现在我遇到使用MaterialUI(Mui)开发前端项目的需求,Mui并没有自己提供表单的验证功能,因此我现在的做法就是寻求第三方库,调研了一段时间后选择了formik+yup的这样一个解决方案,现在已简单的登录页面表单举例:

import { Box, Checkbox, FormControl, FormControlLabel, TextField } from "@mui/material";
import logo from "../assets/imgs/logo.png";
import MatPassword from "../components/common/material/MatPasword";
import * as Yup from "yup";
import { useFormik } from "formik";
import { LoadingButton } from "@mui/lab";
import { LoginParams } from "../models/login.model";

export default function Login() {
  const classes = useStyles();

  const formik = useFormik({
    initialValues: {
      username: "",
      password: "",
      remember: true,
    } as LoginParams,
    validationSchema: Yup.object({
      username: Yup.string().email("Must be a valid email").max(255).required("Email is required"),
      password: Yup.string().max(255).min(5, "sssss").required("Password is required"),
    }),
    onSubmit: (values) => {
      console.log("Send Request", values);
    },
  });

  const login: () => Promise<any> | void = () => {
    formik.handleSubmit();
  };

  return (
    <Box className={classes.container}>
      <div className={classes.content}>
        <div>
          <img src={logo} alt="" width={360} height={130} />
        </div>
        <Box className={classes.formContainer}>
          <div className={classes.title}>Login to your portal</div>
          <form style={{ width: "100%" }} action="">
            <FormControl sx={{ width: 1 / 1, "& .MuiTextField-root": { height: 76 } }}>
              <TextField
                onBlur={formik.handleBlur}
                onChange={formik.handleChange}
                type="email"
                name="username"
                value={formik.values.username}
                error={Boolean(formik.touched.username && formik.errors.username)}
                helperText={formik.touched.username && formik.errors.username}
                margin="normal"
                fullWidth
                label="Email"
                size="medium"
              ></TextField>
            </FormControl>
            <FormControl sx={{ width: 1 / 1, height: 76 }}>
              <MatPassword
                type="password"
                onChange={formik.handleChange}
                onBlur={formik.handleBlur}
                label="password"
                value={formik.values.password}
                error={Boolean(formik.touched.password && formik.errors.password)}
                helperText={formik.touched.password && formik.errors.password}
                fullWidth
                name="password"
                size="medium"
              ></MatPassword>
            </FormControl>
            <FormControl sx={{ width: 1 / 1, height: 56 }}>
              <FormControlLabel control={<Checkbox onBlur={formik.handleBlur} name="remember" onChange={formik.handleChange} checked={formik.values.remember} />} label="remember" />
            </FormControl>
          </form>
          <p> </p>
          <LoadingButton sx={{ mb: 3 }} loading={formik.isSubmitting} fullWidth onClick={login} variant="contained">
            Log in
          </LoadingButton>
          {/*  */}
        </Box>
      </div>
    </Box>
  );
}

注意项

react使用formik的两种形式

从formik官网我们可以了解到在react中使用可以有两种方法:

  1. 使用Formik组件:

    import React from 'react';
    import { Formik, Form, Field } from 'formik';
    import * as Yup from 'yup';
    
    const SignupSchema = Yup.object().shape({
      firstName: Yup.string()
        .min(2, 'Too Short!')
        .max(50, 'Too Long!')
        .required('Required'),
      lastName: Yup.string()
        .min(2, 'Too Short!')
        .max(50, 'Too Long!')
        .required('Required'),
      email: Yup.string().email('Invalid email').required('Required'),
    });
    
    export const ValidationSchemaExample = () => (
      <div>
        <h1>Signup</h1>
        <Formik
          initialValues={{
            firstName: '',
            lastName: '',
            email: '',
          }}
          validationSchema={SignupSchema}
          onSubmit={values => {
            // same shape as initial values
            console.log(values);
          }}
        >
          {({ errors, touched }) => (
            <Form>
              <Field name="firstName" />
              {errors.firstName && touched.firstName ? (
                <div>{errors.firstName}</div>
              ) : null}
              <Field name="lastName" />
              {errors.lastName && touched.lastName ? (
                <div>{errors.lastName}</div>
              ) : null}
              <Field name="email" type="email" />
              {errors.email && touched.email ? <div>{errors.email}</div> : null}
              <button type="submit">Submit</button>
            </Form>
          )}
        </Formik>
      </div>
    );
    
  2. 另一种就是我现在采用的使用useFormik这个hook的模式

    • useFormik传参为formik的配置项,配置项有:

      1. initialValues: form表单的表单域初始值
      2. validationSchema:form表单的校验规则(这个后面和yup讲)
      3. onSubmit:提交表单的方法,注意该配置为一个回调函数,回调函数的参数为values,就是form表单的值,则该方法会在调用的时候将formik的isSubmitting值置为true,这里需要注意返回值,如果返回值是Promise,则在Promise状态变为fullfilled或者rejected的时候将formik的isSubmitting值置为false,否则会在回调调用结束后立即置为false
      4. validateOnChange?: boolean;Tells Formik to validate the form on each input’s onChange event
      5. validateOnBlur?: boolean;Tells Formik to validate the form on each input’s onBlur event
      6. validateOnMount?: boolean;默认值为false, 含义为是否在挂载的时候就校验
      7. isInitialValid?: boolean | ((props: Props) => boolean);Tell Formik if initial form values are valid or not on first render
      8. enableReinitialize?: boolean;Should Formik reset the form when new initialValues change
    • validationSchema:表单的校验规则:这里就是用第三方库yup去生成校验规则:

      validationSchema: Yup.object({
        username: Yup.string().email("Must be a valid email").max(255).required("Email is required"),
        password: Yup.string().max(255).min(5, "sssss").required("Password is required"),
      }),
      

      具体配置和使用就参照yup的官方文档(npm上也能找到文档)

      这里有一点需要特殊说明:

      如果有动态生成表单域以及表单校验规则的需求则需要调用when方法:

      validationSchema: Yup.object({
        screenLockPwd: Yup.number().required(),
        permissionPolicy: Yup.number().required(),
        osUpgrade: Yup.number().required(),
        minPasswordLength: Yup.number().when("screenLockPwd", { is: 1, then: (schema) =>       									schema.required().integer().positive() }),
      }),
          
      // 除此之外,when的第一个参数(依赖项)还可以是数组,以为多个依赖,第二个参数中的is配置项可以是一个值,也可以是一个回调函数
      let schema = object({
        isSpecial: boolean(),
        isBig: boolean(),
        count: number().when(['isBig', 'isSpecial'], {
          is: true, // alternatively: (isBig, isSpecial) => isBig && isSpecial
          then: (schema) => schema.min(5),
          otherwise: (schema) => schema.min(0),
        }),
      });
      
      await schema.validate({
        isBig: true,
        isSpecial: true,
        count: 10,
      });
      

      上面这个例子中,minPasswordLength字段的校验规则为:

      ​ .number(): 必须是number类型的输入

      ​ .when(): 条件判断:

      ​ 参数1: 根据screenLockPwd字段的值进行判断

      ​ 参数2: 判断规则:

      ​ is: 当screenLockPwd字段的值为1的时候

      ​ then: 满足条件执行的配置回调

      ​ otherwise: 不满足条件的时候执行的配置回调

表单域简写

在上面的例子中我么可以看到一个表单域组件需要配置很多的props:

<TextField
  onBlur={formik.handleBlur}
  onChange={formik.handleChange}
  type="email"
  name="username"
  value={formik.values.username}
  error={Boolean(formik.touched.username && formik.errors.username)}
  helperText={formik.touched.username && formik.errors.username}
  margin="normal"
  fullWidth
  label="Email"
  size="medium"
></TextField>;

这其中namevalueonChangeonBlur都是重复的配置项,这里formik提供了getFieldProps方法传入name参数就可以返回这些配置,因此上面的代码可以简写为:

<TextField
  type="email"
  error={Boolean(formik.touched.username && formik.errors.username)}
  helperText={formik.touched.username && formik.errors.username}
  margin="normal"
  fullWidth
  label="Email"
  size="medium"
  {...formik.getFieldProps("username")}
></TextField>;

验证

  1. 直接访问formik的isValid属性,返回bool值
  2. 调用formik的validateField和valdiateForm方法可以校验表单(返回Promise)

你可能感兴趣的:(前端学习笔记和总结,react.js,javascript,前端,formik,react表单)