在我最近开发的React Native项目中,需要用到一个特别常见的功能:表单校验。我需要确保用户在提交表单之前输入了有效的数据,并提供适当的错误提示。在寻找解决方案时,我发现了 React Hook Form。它是一个轻量级且易于使用的表单库,它不仅提供了强大的表单校验功能,还具有良好的扩展性,可以很方便的集成第三方组件。
基本使用
在您的 React Native 项目中,添加以下 React Hook Form 的 React Native 示例代码(react-hook-form.com/get-started…
// App.tsx
import { Text, View, TextInput, Button, Alert } from "react-native"
import { useForm, Controller } from "react-hook-form"
export default function App() {
const {
control,
handleSubmit,
formState: { errors },
} = useForm({
defaultValues: {
firstName: "",
lastName: "",
},
})
const onSubmit = (data) => console.log(data)
return (
<View>
<Controller
control={control}
rules={{
required: true,
}}
render={({ field: { onChange, onBlur, value } }) => (
<TextInput
placeholder="First name"
onBlur={onBlur}
onChangeText={onChange}
value={value}
/>
)}
name="firstName"
/>
{errors.firstName && <Text>This is required.</Text>}
<Controller
control={control}
rules={{
maxLength: 100,
}}
render={({ field: { onChange, onBlur, value } }) => (
<TextInput
placeholder="Last name"
onBlur={onBlur}
onChangeText={onChange}
value={value}
/>
)}
name="lastName"
/>
<Button title="Submit" onPress={handleSubmit(onSubmit)} />
</View>
)
}
在示例代码中,从 react-hook-form 中导出了 useForm 函数 和 Controller 组件,通过调用 userForm 函数来初始化表单的相关状态和方法。
control 是在调用 useForm 函数时初始化的表单控制器对象,control 中管理当前初始化表单的核心API,通过这个 control 对象,我们可以非常方便的管理整个表单的状态。
Controller 就是一个包装器组件,用于管理某个字段的输入和输出。它的工作原理是通过传入的 name 从 control 中获取指定字段相关的属性和方法,并且进行了一层包装,使其更加易于使用,最后通过回调的方式将包装好的属性和方法传递给了 render 属性,然后就可以在 render 的函数实参中获取到这些属性和方法。所以通过 Controller 组件,可以使我们很方便的集成第三方组件。
Controller 组件还可以传入校验规则 rules 字段,支持必填项,最大值,最小值,正则,自定义校验函数等规则。
更多参数细节请查阅官网:Controller 组件.
在上方实例中,从 useForm 函数还解构出来另外两个成员,formState 对象 和 handleSubmit 函数,
formState 对象也是在 useForm 函数调用时初始化的,它其实是要比 control 对象初始化的还要早。上面我们讲了 control 对象的作用就是管理着表单各种核心API也管理着表单的状态,而 formState 对象的作用就是用来记录表单的状态,例如当前表单的字段是否被修改过,是否校验过,是否提交过,以及校验失败的信息(校验的类型,自定义的错误提示)等。正因为有了这些状态的记录,使得我们在碰到一些特殊业务场景可以很轻松的实现。
最后一个就是 handleSubmit 函数,它接收两个函数参数,后者非必填。当调用此函数,会触发表单校验,当校验通过后将会回调第一个参数参数,并且将所有表单字段values传递给该函数。当校验失败则会回调第二个函数参数,并且从上面提到过的 formState 中拿到 errors (校验失败的信息),传递给第二个函数。
const onSubmit = (data) => { console.log('校验通过', data) }
const onError = (errors) => { console.log('校验失败', errors) }
<Button title="Submit" onPress={handleSubmit(onSubmit, onError)} />
以上就是 React Hook Form 在 React Native 中的基础使用。
虽然 React Hook Form 已经非常强大了,但我觉得实际业务搬砖中,目前这样子写起来还是比较繁琐,例如错误信息,我需要关心它是否显示错误信息,并且还要管理它的样式,有10个需要校验的表单字段,我就需要把代码拷贝10次,虽然当时拷贝起来比较省心,但是后期维护起来其实是很不方便的。
因为在实际的业务中表单字段的错误样式 UI / UX 基本是相同的,如果不相同那就干UI(狗头保命)。从长远的摸鱼之计,提高代码的可维护性是非常重要的,毕竟 Fix bug / Change request 的时长决定了我们摸鱼的时长。
接下来会基于 React Hook Form 封装一个 FormItem 组件,该组件内部预设好统一的错误提示,统一的 label 样式,这样我们在开发时只需要关心 render 的逻辑即可。
封装 FormItem
效果图:
// App.tsx
import {
Text,
View,
TextInput,
Button,
Alert,
TextInputProps,
StyleSheet
} from 'react-native'
import { useForm } from 'react-hook-form'
import FormItem from './src/components/FormItem'
const emailRegEx =
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
const Input = (props: TextInputProps) => {
return (
<TextInput
{...props}
style={{
fontSize: 20,
height: 40,
paddingLeft: 10
}}
/>
)
}
export default function App() {
const {
control,
handleSubmit,
formState: { errors }
} = useForm({
defaultValues: {
email: '',
password: ''
}
})
const onSubmit = () => {
Alert.alert('提交成功~💐')
}
return (
<View style={styles.wrapper}>
<Text style={styles.title}>Sign in</Text>
<FormItem
required
name="email"
label="Email"
control={control}
errors={errors.email}
rules={{
required: '请输入邮箱',
pattern: {
value: emailRegEx,
message: '请输入一个有效的邮箱'
}
}}
render={({ field: { onChange, value } }) => (
<Input
value={value}
onChangeText={onChange}
placeholder="请输入邮箱"
/>
)}
style={{ marginBottom: 40 }}
/>
<FormItem
required
label="Password"
control={control}
name="password"
rules={{
required: '请输入密码'
}}
errors={errors.password}
render={({ field: { onChange, value } }) => (
<Input
value={value}
onChangeText={onChange}
placeholder="请输入密码"
secureTextEntry
/>
)}
/>
<Button title="Sign in" onPress={handleSubmit(onSubmit)} />
</View>
)
}
const styles = StyleSheet.create({
wrapper: {
flex: 1,
justifyContent: 'center',
paddingLeft: 15,
paddingRight: 15
},
title: {
marginBottom: 30,
fontSize: 50
}
})
// src/components/FormItem/index.tsx
import React from 'react'
import {
Controller,
ControllerProps,
FieldValues,
UseControllerProps,
GlobalError,
FieldPath
} from 'react-hook-form'
import { TextStyle, View, Text, ViewStyle } from 'react-native'
type FormItemProps<T extends FieldValues, TName extends FieldPath<T>> = {
label?: string
required?: boolean
errors?: GlobalError
style?: ViewStyle
labelStyle?: TextStyle
border?: boolean
} & ControllerProps<T, TName> &
UseControllerProps<T, TName>
const FormItem = <T extends FieldValues, TName extends FieldPath<T>>(
props: FormItemProps<T, TName>
) => {
const {
name,
control,
rules,
label,
required,
errors,
style = {},
labelStyle = {},
border = true,
render
} = props
return (
<View key={name} style={style}>
{label && (
<View
style={{
flexDirection: 'row',
alignItems: 'center'
}}
>
<Text
style={{
fontSize: 20,
marginBottom: 5,
fontWeight: '700',
...labelStyle
}}
>
{label}
</Text>
{required && (
<Text style={{ marginLeft: 4, color: 'red', fontSize: 20 }}>*</Text>
)}
</View>
)}
<View
style={{
borderWidth: 1,
...(!errors
? {
borderColor: border ? '#B3BAC1' : 'transparent'
}
: {
borderColor: border ? '#D52D0B' : 'transparent'
})
}}
>
<Controller
name={name}
control={control}
rules={rules}
render={render}
/>
</View>
{rules && errors && errors?.message && (
<View
style={{
marginTop: 4
}}
>
<Text
style={{
color: 'red'
}}
>
{errors?.message}
</Text>
</View>
)}
</View>
)
}
export default FormItem
以上就是 FormItem 组件及示例代码并且包含 Typescript 的类型,复制到您的项目中就可以运行,然后您可以根据您的具体业务和 UI / UX 进行调整。
FormItem 就是一个非常简单的封装,核心组件还是我们之前提到过的 Controller 包装器组件,只不过是多了一些通用的自定义元素,例如我这里结合自己的需求扩展了 label 和 error,这样就大大提高了代码的可维护性,例如 UI/UX 修改了表单校验的样式,这样我就可以直接修改 FormItem 组件即可。
END 🏄🏻
感谢阅读 :P