formik看这篇文章就够了

本篇文章将带您从了解formik到掌握,最后熟练使用。本篇文章的code您可以在gitee中获取:https://gitee.com/xifeng-canyang/jest-formik/tree/master/src/page/formik

  • 何为formik
  • 与Redux-Form比较
  • 安装
  • show Time
    • 基本例子
    • 校验
    • yup校验
    • Formik组件
    • array or object formik
    • 常用api
      • ErrorMessage
      • Field
      • getIn()
      • setValues()和setFieldValue()
  • 案例
    • 来一个简单的
    • 再来一个难一点的

何为formik

什么是formik呢?Formik 是一小群 React 组件和钩子,用于在 React 和 React Native 中构建表单。它有助于解决三个最烦人的部分,帮助我们解决了表单中的三个核心问题:

  • 从表单状态获取值和从表单状态获取值
  • 验证和错误消息
  • 处理表单提交
    由此可知,Formik 将使事情井井有条——使测试、重构和推理表单变得轻而易举。

与Redux-Form比较

  • Redux-Form 在每次按键时都会多次调用整个顶级 Redux 减速器。这对于小型应用程序来说很好,但是随着您的 Redux 应用程序的增长,如果您使用 Redux-Form,输入延迟将继续增加。
  • Redux-Form 压缩压缩后大小为 22.5 kB(Formik 为 12.7 kB)
  • Formik 的目标是创建一个可扩展、高性能的表单助手

安装

依赖包安装

npm install formik --save

cdn集成

<script src="https://unpkg.com/formik/dist/formik.umd.production.min.js"></script>

添加此内容后,您将可以访问变量window.Formik<Insert_Component_Name_Here>。

show Time

基本例子

先来看一个简单的表单例子,新建文件src\page\formik\com\basic-example.tsx:

import React from 'react';
import { useFormik } from 'formik';
import { validate1 } from './common/utils';
const SignupForm = () => {
  // Pass the useFormik() hook initial form values and a submit function that will
  // be called when the form is submitted
  const formik = useFormik({
    initialValues: {
      email: '',
      firstName: '',
      lastName: '',
    },
    validate: validate1,
    onSubmit: values => {
      alert(JSON.stringify(values, null, 2));
    },
  });
  return (
    <form onSubmit={formik.handleSubmit}>
        <label htmlFor="firstName">First Name</label>
        <input
            id="firstName"
            name="firstName"
            type="text"
            onChange={formik.handleChange}
            value={formik.values.firstName}
        />
        {formik.errors.firstName ? <div>{formik.errors.firstName}</div> : null} <br /> 

        <label htmlFor="lastName">Last Name</label>
        <input
            id="lastName"
            name="lastName"
            type="text"
            onChange={formik.handleChange}
            value={formik.values.lastName}
        />
        {formik.errors.lastName ? <div>{formik.errors.lastName}</div> : null} <br /> 

        <label htmlFor="email">Email Address</label>
        <input
            id="email"
            name="email"
            type="email"
            onChange={formik.handleChange}
            value={formik.values.email}
        />
      
        {formik.errors.email ? <div>{formik.errors.email}</div> : null} <br /> 

        <button type="submit">Submit</button>
    </form>
  );
};
export default SignupForm;

解释

  • handleSubmit:提交处理程序
  • handleChange<input>:传递给每个、<select>、 或的更改处理程序<textarea>
  • values:我们表单的当前值
  • handleChange我们为每个 HTML 输入重用相同的更改处理函数
  • 我们传递一个id与我们定义的属性相匹配的nameHTML 属性initialValues
  • 我们使用相同的名称(email-> formik.values.email)访问字段的值
    假如我们没有认识formik这个东西,那么我们监测受控组件就得一个个写change事件。

校验

Formik 不仅跟踪您的表单values,还跟踪其验证和错误消息。要使用 JS 添加验证,让我们指定一个自定义验证函数并将其传递给validate钩子useFormik()。如果存在错误,这个自定义验证函数应该生成一个与我们的/error形状匹配的对象。
在前面的例子basic-example.tsx当中,我们引入了一个validate的文件,因此,我们需要新建文件:src\page\formik\com\common\utils.ts

export const validate1 = (values: any) => {
    const errors = {} as any;
    if (!values.firstName) {
      errors.firstName = 'Required';
    } else if (values.firstName.length > 15) {
      errors.firstName = 'Must be 15 characters or less';
    }
    if (!values.lastName) {
      errors.lastName = 'Required';
    } else if (values.lastName.length > 20) {
      errors.lastName = 'Must be 20 characters or less';
    }
    if (!values.email) {
      errors.email = 'Required';
    } else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)) {
      errors.email = 'Invalid email address';
    }
    return errors;
};

默认情况下,Formik 将在每次击键(更改事件)、每个输入的模糊事件以及提交之前进行验证。校验确实实现了,但是如果我们希望在提交表单的时候再显示错误,需要怎么做呢?
Formik 会跟踪哪些字段已被访问过。它将这些信息存储在一个名为 touched 的对象中,同时也反映了values/initialValues的值,这些值为boolean值。
为了利用touched,我们传递formik.handleBlur给每个输入的onBlurprop。此函数的工作原理与它类似formik.handleChange,它使用name属性来确定要更新那个字段。
我们新建文件src\page\formik\com\basic-example-touched.tsx

import React from 'react';
import { useFormik } from 'formik';

import { validate1 } from './common/utils';

const SignupForm = () => {
  // Pass the useFormik() hook initial form values and a submit function that will
  // be called when the form is submitted
  const formik = useFormik({
    initialValues: {
      email: '',
      firstName: '',
      lastName: '',
    },
    validate: validate1,
    onSubmit: values => {
      alert(JSON.stringify(values, null, 2));
    },
  });
  return (
    <form onSubmit={formik.handleSubmit}>
        <label htmlFor="firstName">First Name</label>
        <input
            id="firstName"
            name="firstName"
            type="text"
            onChange={formik.handleChange}
            // onBlur={formik.handleBlur}
            value={formik.values.firstName}
        />
            {formik.touched.firstName && formik.errors.firstName ? (
                <div>{formik.errors.firstName}</div>
            ) : null}  <br />

        <label htmlFor="lastName">Last Name</label>
        <input
            id="lastName"
            name="lastName"
            type="text"
            onChange={formik.handleChange}
            // onBlur={formik.handleBlur}
            value={formik.values.lastName}
        />
        {formik.touched.lastName && formik.errors.lastName ? (
            <div>{formik.errors.lastName}</div>
        ) : null} <br />

        <label htmlFor="email">Email Address</label>
        <input
            id="email"
            name="email"
            type="email"
            onChange={formik.handleChange}
            // onBlur={formik.handleBlur}
            value={formik.values.email}
        />
      
        {/* {formik.errors.email ? <div>{formik.errors.email}</div> : null} */}
        {formik.touched.email && formik.errors.email ? (
            <div>{formik.errors.email}</div>
        ) : null} <br />
        <button type="submit">Submit</button>
    </form>
  );
};
export default SignupForm;

yup校验

由于 Formik 作者/用户非常喜欢Yup,因此 Formik 有一个名为 Yup 的特殊配置道具validationSchema,它会自动将 Yup 的验证错误消息转换为一个漂亮的对象,其键匹配values
安装

npm install yup --save

我们新建文件:src\page\formik\com\basic-validate.tsx

import React from 'react';
import { useFormik } from 'formik';

import * as Yup from 'yup';

const SignupFormValidate = () => {
  // Pass the useFormik() hook initial form values and a submit function that will
  // be called when the form is submitted
  const formik = useFormik({
    initialValues: {
      email: '1@qq.com',
      firstName: 'david',
      lastName: 'zhu',
    },
    validationSchema: Yup.object({
      firstName: Yup.string()
        .max(15, 'Must be 15 characters or less')
        .required('Required'),
      lastName: Yup.string()
        .max(20, 'Must be 20 characters or less')
        .required('Required'),
      email: Yup.string().email('Invalid email address').required('Required'),
    }),
    onSubmit: values => {
      alert(JSON.stringify(values, null, 2));
    },
  });
  return (
    <form onSubmit={formik.handleSubmit}>
        <label htmlFor="firstName">First Name: </label>
        <input
            id="firstName"
            name="firstName"
            type="text"
            onChange={formik.handleChange}
            onBlur={formik.handleBlur}
            value={formik.values.firstName}
            //{...formik.getFieldProps('firstName')}

        />
        {formik.touched.firstName && formik.errors.firstName ? (
            <div>{formik.errors.firstName}</div>
        ) : null}  <br />

        <label htmlFor="lastName">Last Name: </label>
        <input
            id="lastName"
            name="lastName"
            type="text"
            onChange={formik.handleChange}
            onBlur={formik.handleBlur}
            value={formik.values.lastName}
        />
        {formik.touched.lastName && formik.errors.lastName ? (
            <div>{formik.errors.lastName}</div>
        ) : null} <br />

        <label htmlFor="email">Email Address: </label>
        <input
            id="email"
            name="email"
            type="email"
            onChange={formik.handleChange}
            onBlur={formik.handleBlur}
            value={formik.values.email}
        />
      
        {/* {formik.errors.email ? <div>{formik.errors.email}</div> : null} */}
        {formik.touched.email && formik.errors.email ? (
            <div>{formik.errors.email}</div>
        ) : null} <br />
        <button type="submit">Submit</button>
    </form>
  );
};
export default SignupFormValidate;

yup的使用让我们看到了,功能不变的情况下代码可以更加的简洁。

Formik组件

以上我们所有的例子都是通过useFormik拿到我们控制formik表单的内容,但是这样不容易形成封装,也就是说我们无法进行实例传值。而Formik由于配备了React Context,因此Formik本身就可以管理所包裹的JSX
我们新建文件src\page\formik\com\basic-formik.tsx:

import React from 'react';
import { Formik } from 'formik';
import * as Yup from 'yup';

const SignupFormFormik = () => {
  return (
    <Formik
      initialValues={{ firstName: '', lastName: '', email: '' }}
      validationSchema={Yup.object({
        firstName: Yup.string()
          .max(15, 'Must be 15 characters or less')
          .required('Required'),
        lastName: Yup.string()
          .max(20, 'Must be 20 characters or less')
          .required('Required'),
        email: Yup.string().email('Invalid email address').required('Required'),
      })}
      onSubmit={(values, { setSubmitting }) => {
        setTimeout(() => {
          alert(JSON.stringify(values, null, 2));
          setSubmitting(false);
        }, 400);
      }}
    >
      {formik => (
        <form onSubmit={formik.handleSubmit}>
          <label htmlFor="firstName">First Name</label>
          <input
            id="firstName"
            type="text"
            {...formik.getFieldProps('firstName')}
          />
          {formik.touched.firstName && formik.errors.firstName ? (
            <div>{formik.errors.firstName}</div>
          ) : null} <br />
          
          <label htmlFor="lastName">Last Name</label>
          <input
            id="lastName"
            type="text"
            {...formik.getFieldProps('lastName')}
          />
          {formik.touched.lastName && formik.errors.lastName ? (
            <div>{formik.errors.lastName}</div>
          ) : null} <br />

          <label htmlFor="email">Email Address</label>
          <input id="email" type="email" {...formik.getFieldProps('email')} />
          {formik.touched.email && formik.errors.email ? (
            <div>{formik.errors.email}</div>
          ) : null} <br />

          <button type="submit">Submit</button>
        </form>
      )}
    </Formik>
  );
};
export default SignupFormFormik;

以上,我们可以用formik独有的Field来简写

import React from 'react';
import { Formik, Field, Form, ErrorMessage } from 'formik';
import * as Yup from 'yup';

const SignupFormFormikField = () => {
  return (
    <Formik
      initialValues={{ firstName: '', lastName: '', email: '' }}
      validationSchema={Yup.object({
        firstName: Yup.string()
          .max(15, 'Must be 15 characters or less')
          .required('Required'),
        lastName: Yup.string()
          .max(20, 'Must be 20 characters or less')
          .required('Required'),
        email: Yup.string().email('Invalid email address').required('Required'),
      })}
      onSubmit={(values, { setSubmitting }) => {
        setTimeout(() => {
          alert(JSON.stringify(values, null, 2));
          setSubmitting(false);
        }, 400);
      }}
    >
      <Form>
        <label htmlFor="firstName">First Name</label>
        <Field name="firstName" type="text" />
        <ErrorMessage name="firstName" /> <br />

        <label htmlFor="lastName">Last Name</label>
        <Field name="lastName" type="text" />
        <ErrorMessage name="lastName" /><br />

        <label htmlFor="email">Email Address</label>
        <Field name="email" type="email" />
        <ErrorMessage name="email" /><br />

        <button type="submit">Submit</button>
      </Form>
    </Formik>
  );
};
export default SignupFormFormikField;

所以我们在明白了forimik是如何运行之后,后续的封装也是基于Formik来处理,会单独讲解。
前面的例子的values值都只是一个普通对象,如果这个对象是深度的,那么该如何处理呢

array or object formik

Formik 开箱即用地支持嵌套对象和数组
Formik 中的props name可以使用类似 lodash 的点路径来引用嵌套的 Formik 值。这意味着您不再需要展平表单的值。
我们新建文件src\page\formik\com\basic-array-obj.tsx:

import React from 'react';
import { Formik, Field, Form, ErrorMessage } from 'formik';
import * as Yup from 'yup';
const BasicForArrayObj = () => {
  return (
    <Formik
      initialValues={
        { 
          firstName: '', 
          lastName: '', 
          email: '',
          social: {
            facebook: '',
            twitter: '',
          } 
        }
      }
      validationSchema={Yup.object({
        social: Yup.object({
          facebook: Yup.string()
          .max(15, 'Must be 15 characters or less')
          .required('Required'),
          twitter: Yup.string()
          .max(15, 'Must be 15 characters or less')
          .required('Required'),
        }),
        firstName: Yup.string()
          .max(15, 'Must be 15 characters or less')
          .required('Required'),
        lastName: Yup.string()
          .max(20, 'Must be 20 characters or less')
          .required('Required'),
        email: Yup.string().email('Invalid email address').required('Required'),
      })}
      onSubmit={(values, { setSubmitting }) => {
        setTimeout(() => {
          alert(JSON.stringify(values, null, 2));
          setSubmitting(false);
        }, 400);
      }}
    >
      <Form>

        <label htmlFor="twitter">facebook</label>
        <Field name="social.twitter" type="text" />
        <ErrorMessage name="social.twitter" /> <br />

        <label htmlFor="facebook">facebook</label>
        <Field name="social.facebook" type="text" />
        <ErrorMessage name="social.facebook" /> <br />

        <label htmlFor="firstName">First Name</label>
        <Field name="firstName" type="text" />
        <ErrorMessage name="firstName" /> <br />

        <label htmlFor="lastName">Last Name</label>
        <Field name="lastName" type="text" />
        <ErrorMessage name="lastName" /><br />

        <label htmlFor="email">Email Address</label>
        <Field name="email" type="email" />
        <ErrorMessage name="email" /><br />

        <button type="submit">Submit</button>
      </Form>
    </Formik>
  );
};
export default BasicForArrayObj;

注意

  • yup真丢对象校验使用Yup.object({})
  • 访问属性值通过点属性字符串

同理数组也是类似的做法,我们新建文件src\page\formik\com\basic-array-obj1.tsx:

import React from 'react';
import { Formik, Field, Form, ErrorMessage } from 'formik';
import * as Yup from 'yup';

const BasicForArrayObj2 = () => {
  return (
    <Formik
      initialValues={
        { 
          friends: ['', ''], 
        }
      }
      validationSchema={Yup.object({
        friends: Yup.array().of(Yup.string().required('Required')),
      })}
      onSubmit={(values, { setSubmitting }) => {
        setTimeout(() => {
          alert(JSON.stringify(values, null, 2));
          setSubmitting(false);
        }, 400);
      }}
    >
      <Form>

        <label htmlFor="friends[1]">friend2</label>
        <Field name="friends[1]" type="text" />
        <ErrorMessage name="friends[1]" /> <br />

        <label htmlFor="friends[0]">ftiend1</label>
        <Field name="friends[0]" type="text" />
        <ErrorMessage name="friends[0]" /> <br />

        <button type="submit">Submit</button>
      </Form>
    </Formik>
  );
};
export default BasicForArrayObj2;

注意:

常用api

ErrorMessage

捕捉错误的容器,相当于下列的简写,用法见上面的例子,必传一个name属性。

{formik.touched.lastName && formik.errors.lastName ? (
    <div>{formik.errors.lastName}</div>
) : null} <br />

Field

将自动将输入连接到 Formik,默认渲染是input输入框,他有以下几种方法渲染
直接渲染

<Field type="email" name="email" placeholder="Email" />

as转化成其他节点

<Field as="select" name="color">
  <option value="red">Red</option>
  <option value="green">Green</option>
  <option value="blue">Blue</option>
</Field>

渲染jsx

<Field name="lastName">
{({
  field, // { name, value, onChange, onBlur }
  form: { touched, errors }, // also values, setXXXX, handleXXXX, dirty, isValid, status, etc.
  meta,
}) => (
  <div>
    <input type="text" placeholder="Email" {...field} />
    {meta.touched && meta.error && (
      <div className="error">{meta.error}</div>
    )}
  </div>
)}
</Field>

以component组件形式渲染

<Field name="lastName" placeholder="Doe" component={MyInput} />

getIn()

获取点属性的值,在values包含数组或者深层次对象的时候有用,可以对比上述案例。

setValues()和setFieldValue()

setValues()设置formik的values属性值,setFieldValue()设置values的某一个属性值,二者写法不同,但是目的一样:改变values的值。使用他们之前最好配套useFormik使用:

formik.setFieldValue('firstName','zhangsan');
formik.setValues({
    email: '',
    firstName: 'lisi',
    lastName: '',
})

案例

主要讲解了如何封装formik,初始化formik,formik和Mui的使用,以及表单提交

来一个简单的

首先建立一个form.tsx ,他会接收一个formik实例,本质上用到了React.Provider,确保了在其包裹内可以使用useFormik进行获取对应的values值。

import type { FormikConfig,FormikValues,FormikContextType } from 'formik';
import type { ReactNode } from 'react';
import { FormikProvider,Form, } from 'formik';
import React from 'react';

interface FormProps<FormikValues> {
    instance: FormikContextType<FormikValues>,
    children: ReactNode
}

const ZLForm = <FormikValues extends object>(props: FormProps<FormikValues>) => {
    const { instance,children } = props;
    return (
        <FormikProvider value={instance}>
            <Form>
                {children}
            </Form>
        </FormikProvider>
    )
}
export default ZLForm;

然后建立一个功能组件wrapper.tsx
初始化formik

 const testFormik = useFormik<Person>({
    initialValues: {
      username: 'jack',
      age: 12
    },
    onSubmit:(values) => {
      console.log('i am submit',values)
    }
  })

获取values

const { values,setFieldValue } = testFormik;

设置初始值,初始值如果不使用的话,便会使用默认的initialValues

  useEffect(() => {
    setFieldValue('username',initialValue.username);
    setFieldValue('age',initialValue.age);
  },[])

主要业务结构,这里需要注意使用受控组件,即表单的value值和他的onChange事件要对上来;当我们input框发生改变的时候,会触发testFormik.handleChange;type='submit'相当于表单form的submit,会触发formik的onSubmit事件

另外ZLInput是封装的组件,见gitee:https://gitee.com/xifeng-canyang/jest-formik/blob/master/src/component/common/Input.tsx

  <ZLForm instance={testFormik}>
    <Grid container>
      <Grid item xs={12}>
        <ZlInput name='username' label='username' value={values.username} handleInputChange={testFormik.handleChange} />
        <ZlInput name='age' label='age' value={values.age} handleInputChange={testFormik.handleChange} />
      </Grid>
      <Grid item xs={12}>
        <Button variant="contained" type='submit'>submit</Button>
      </Grid>
    </Grid> 
  </ZLForm>
示例图

完整的demo:https://gitee.com/xifeng-canyang/jest-formik/blob/master/src/page/formik/advance/wrapper.tsx

再来一个难一点的

难一点是指,我们的初始值不在是一个简单的对象,而是对象、数组、深层次对象和数组对象的结合体。本例子可以在gitee上找到:https://gitee.com/xifeng-canyang/jest-formik/blob/master/src/page/formik/advance/wrapper-complex.tsx

注意点:

  • gitee代码是都写在了一个文件里面,因此显得业务比较臃肿,实际开发建议抽出公共组件,从而降低代码的复杂度
  • 对于深层次的数据展示我们是使用点运算符
  • 对于深层次数据的获取则是使用getIn

我们新建一个文件wrapper-complex

初始值示例

const testFormik = useFormik<Person>({
  initialValues: {
    username: 'jack',
    age: 12,
    like: {
      sport: 'yes',
      pingPong: 'no'
    },
    subject: ['math','english','chinese'],
    subjectDetail: [
      {
        subject: 'math',
        score: 80
      },
      {
        subject: 'english',
        score: 76
      },
      {
        subject: 'chinese',
        score: 95
      }
    ]
  },
  onSubmit:(values) => {
    console.log('i am submit',values)
  }
})

简单的对象展示

<Grid item xs={12}>
    <ZlInput name='username' label='username' value={values.username} handleInputChange={testFormik.handleChange} />
    <ZlInput name='age' label='age' value={values.age} handleInputChange={testFormik.handleChange} />
</Grid>

深层次的对象展示

<Grid item xs={12}>
{
  Object.keys(values.like).map(subkey => {
    return (
        <ZlInput key={subkey} name={`like.${subkey}`} label={getIn(values,`like.${subkey}`)} value={getIn(values,`like.${subkey}`)} handleInputChange={testFormik.handleChange} />
    )
  })
}
</Grid>

数组展示

<Grid item xs={12}>
{
  values.subject.map((item,index) => {
    return (
      <ZlInput key={item} name={`subject[${index}]`} label={getIn(values,`subject[${index}]`)} value={getIn(values,`subject[${index}]`)} handleInputChange={testFormik.handleChange} />

    )
  })
}
</Grid>

数组对象结合展示

<Grid container>
{
  values.subjectDetail.map((item,index) => {
    const keyArr = Object.keys(item);
    const uniKey = JSON.stringify(item);
    return (
      <Title key={uniKey}>
        <Title>科目:{item.subject}</Title>
        <ZlInput name={`subjectDetail[${index}].${keyArr[1]}`} label={getIn(values,`subjectDetail[${index}].${keyArr[1]}`)} value={getIn(values,`subjectDetail[${index}].${keyArr[1]}`)} handleInputChange={testFormik.handleChange} />

      </Title>
    )
  })
}
</Grid>

效果图


效果图

看到这里也不容易,帮忙点点赞支持支持~

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容