在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>
);
}
但是显然这中方式在实战中的庞大表单结构中会显得非常无力和麻烦。
如果使用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);
现在我遇到使用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>
);
}
从formik官网我们可以了解到在react中使用可以有两种方法:
使用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>
);
另一种就是我现在采用的使用useFormik这个hook的模式
useFormik传参为formik的配置项,配置项有:
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>;
这其中name
、value
、onChange
、onBlur
都是重复的配置项,这里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>;