受控表单是指表单元素的值受 React 组件的 state 或 props 控制。
特点:
.表单元素的值保存在组件的 state 中,以便在需要时进行访问、验证或提交。
.每当用户输入发生变化时,需要手动更新 state 来反映新的值。
.可以通过 state 的值来进行表单元素的验证,并提供实时的错误提示。
import React, { useState } from 'react'; function ControlledForm() { const [phone, setPhone] = useState(''); const handlePhoneChange = (e) => { setName(e.target.value); } const handleSubmit = (e) => { e.preventDefault(); // 处理表单提交逻辑 } return ( <form onSubmit={handleSubmit}> <label> Phone: <input type="text" value={phone} onChange={handlePhoneChange} /> </label> <button type="submit">Submit</button> </form> ); } export default ControlledForm;2.2 非受控表单的特点和使用场景
非受控表单是指表单元素的值不受 React 组件的 state 或 props 控制,而是将表单数据交给 DOM 节点来处理,可以使用 Ref 来获取数据。
特点:
.表单元素的值不会保存在组件的 state 中,而是通过 DOM 来获取。
.可以通过 ref 来获取表单元素的值,而不需要手动更新 state。import React, { useRef } from 'react'; // 堆代码 duidaima.com function UncontrolledForm() { const nameInputRef = useRef(null); const handleSubmit = (e) => { e.preventDefault(); const name = nameInputRef.current.value; } return ( <form onSubmit={handleSubmit}> <label> Name: <input type="text" ref={nameInputRef} /> </label> <button type="submit">Submit</button> </form> ); } export default UncontrolledForm;2.3 对比受控和非受控表单的差异
特点 | 受控表单 | 非受控表单 |
---|---|---|
value 管理 | 🙆受控表单元素的值保存在组件的 state 中,方便访问和操作 | 🙅非受控组件需要依赖 ref 来获取元素值,并且会受到组件生命周期变更而影响值 |
验证和实时性 | 🙆可以实时验证和处理用户输入 | 🙅不利于实时反映用户输入的值,不方便对用户输入进行验证和处理 |
表单的整体控制 | 🙆对表单数据有更好的控制 | 🙅对表单数据的控制有限 |
数据流 | 🙆可以根据表单元素的值动态地改变其他组件的状态或行为 | 🙅需要通过 ref 来获取表单元素的值,不符合 React 的数据流思想。 |
代码复杂性 | 🙅需要更多的代码来处理表单元素的变化和验证。对于复杂的表单,可能会引入大量的 state 和事件处理函数,导致代码冗长。 | 🙆代码量较少,不需要处理 state 的变化。对于简单的表单,可以更快地实现功能。 |
dom更新性能 | 🙅 频繁的 setState 触发视图的重新渲染可能会导致性能问题。 | 🙆通过 defaultValue 来设置组件的默认值,它仅会被渲染一次,在后续的渲染时并不起作用 |
使用场景 | 基本为最佳实践 | 一般作为简易实现 |
// rc-form-field // Field.tsx public reRender() { if (!this.mounted) return; this.forceUpdate(); } ..... public onStoreChange: FieldEntity['onStoreChange'] = (prevStore, namePathList, info) => { ... case 'remove': { if (shouldUpdate) { this.reRender(); return; } break; } case 'setField': { if (namePathMatch) { const { data } = info; // FieldData 处理,touched/warning/error/validate ... this.dirty = true; this.triggerMetaEvent(); // setField 时 field 绑定 name 匹配时强制更新 this.reRender(); return; } // setField 携带 shouldUpdate 的控件时更新 if ( shouldUpdate && !namePath.length && requireUpdate(shouldUpdate, prevStore, store, prevValue, curValue, info) ) { this.reRender(); return; } break; case 'dependenciesUpdate': { /** * 堆代码 duidaima.com * 当标记了的`dependencies`更新时触发. 相关联的`Field`会更新 */ const dependencyList = dependencies.map(getNamePath); // No need for `namePathMath` check and `shouldUpdate` check, since `valueUpdate` will be // emitted earlier and they will work there // dependencies 不应和 shouldUpdate 一起使用,可能会导致没必要的 rerender if (dependencyList.some(dependency => containsNamePath(info.relatedFields, dependency))) { this.reRender(); return; } break; } default: if ( namePathMatch || ((!dependencies.length || namePath.length || shouldUpdate) && requireUpdate(shouldUpdate, prevStore, store, prevValue, curValue, info)) ) { this.reRender(); return; } break;
不同于 rc-field-form 中使用的受控表单来做表单状态管理,react-hook-form 使用了 React 的 useRef 和 useReducer 来处理表单数据的状态,而不是使用 React 的 useState 来追踪表单数据的变化。具备非受控表单的优点以提高性能,并使代码更简洁。
react-hook-form 的最简 demo 如下:
import React from "react"; import { useForm } from "react-hook-form"; function MyForm() { const onSubmit = (data) => { console.log(data); }; const { register, handleSubmit, formState: { errors } } = useForm(); return ( <form onSubmit={handleSubmit(onSubmit)}> <input {...register("firstName", { required: true })} /> {errors.firstName && <p>First name is required.</p>} <input {...register("lastName", { required: true })} /> {errors.lastName && <p>Last name is required.</p>} <button type="submit">Submit</button> </form> ); }为什么会说 react-hook-form 提供的是一个非受控表单,其实就需要细究一下这个 ...register 到底返回了什么
// react-hook-form createFormControl const register: UseFormRegister<TFieldValues>