使用 React Hook 表单控制器将任何内容转换为表单字段
进入控制器
制作字段组件
真实案例
结论
封面图片由Chris J. Davis在Unsplash上拍摄
React Hook Form很快就成了我最喜欢的处理各种形状和大小表单的库,主要是因为它出色的开发体验。他们主页上 30 秒的截屏视频很好地演示了如何使用 的魔力register
连接每个字段,将其集成到标准表单中。使用原生<input/>
组件时,启动和运行起来非常简单。
但在现实世界中,我们通常不会使用原生输入。流行的 UI 库通常会抽象并包装所有底层表单元素,这使得使用 变得困难甚至无法实现register
。
有时,我们希望通过自定义交互组件来提升用户的体验,比如用 5 个真正的星形图标来评价产品,而不是使用枯燥的选择框。那么,如何才能将这些组件与现有表单轻松关联,避免逻辑混乱呢?
进入控制器
该库导出了一个<Controller/>
专为此目的而制作的组件。它允许我们将任何组件连接到表单,使其能够显示和设置其值。
要使用它,你需要从而不是control
返回的对象。此外,像往常一样,你需要一个来告诉表单我们正在控制哪个字段。最后,prop 是我们放置组件的地方。useForm()
register
name
render
// Controller syntax
const { control } = useForm();
return (
<Controller
control={control}
name="myField"
render={/* Custom field component goes here */}
/>
);
制作字段组件
为什么这样称呼Controller
?可能是因为我们的字段组件需要成为一个受控组件。
简而言之,受控组件是通过 props 获取和设置其当前“状态”的组件。对于表单字段来说,该状态就是该字段的当前值。
<input/>
是一个可控制组件的例子。我们告诉输入它的当前值是什么,并让它知道何时应该更改该值。
// <input/> as a controlled component in a standard React form
const [val, setVal] = useState('')
return (
<input
type="text"
value={val}
onChange={e => setVal(e.target.value)}
/>
)
这里我们看到了使我们的字段组件与控制器一起工作所需的两个道具:
value
- 它应该显示该字段的当前值。onChange
- 它应该能够通知控制器当前值何时发生变化。
这也恰好是函数传递给我们的两个属性render
!它的签名包括一个field
具有value
和onChange
(以及其他东西)的对象。
使用控制器进行基本输入没有多大意义,但这里是为了说明目的:
// Using a basic input in a Controller
// (though you can just use `register` here)
const { control } = useForm();
return (
<>
<Controller
control={control}
name="myField"
render={({ field: { value, onChange }}) => (
<input value={value} onChange={onChange} />
)}
/>
</>
)
注意:如果您使用的是 React Hook Form V6 或更早版本,此处的函数签名略有不同。
value
并且onChange
是参数的顶级属性,如下所示。// V6 or earlier render=({ value, onChange }) => ( <input value={value} onChange={onChange} /> )
真实案例
使用 UI 库:Material UI
许多项目使用流行的 UI 库(例如Material UI)的表单输入。问题在于,这些<input/>
组件通常对我们隐藏,因此我们无法将register
它们连接到表单。这时,Controller 就派上用场了!
通常,字段会使用相同的value
proponChange
名称。如果是这种情况,我们可以简单地将{...field}
对象展开到组件中。
其他时候,props 的命名可能不一样。例如,Checkbox 接受的值是checked
而不是value
。这意味着我们无法轻易地field
向其中扩展,但结果仍然相当容易组合。
export default function App() {
const { control, handleSubmit } = useForm({
defaultValues: {
textField: "",
checkbox: false
}
});
const onSubmit = (values) => alert(JSON.stringify(values));
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
control={control}
name="textField"
render={({ field }) => (
// Material UI TextField already supports
// `value` and `onChange`
<TextField {...field} label="Text field" />
)}
/>
<Controller
control={control}
name="checkbox"
render={({ field: { value, onChange } }) => (
// Checkbox accepts its value as `checked`
// so we need to connect the props here
<FormControlLabel
control={<Checkbox checked={value} onChange={onChange} />}
label="I am a checkbox"
/>
)}
/>
<Button type="submit" variant="contained" color="primary">
Submit
</Button>
</form>
);
}
从零开始构建:五星级评级领域
我们可能都用过那种随处可见的小部件,它允许我们通过点击一排星形图标来对任何内容进行评分。值得庆幸的是,如果我们能够创建一个受控组件,就可以将其整齐地嵌入到表单的其余部分中。
// StarButton displays a single star
// It is controlled via active and onClick props
const StarButton = ({ active, onClick }) => (
<button type="button" onClick={onClick}>
{active ? <Star color="secondary" /> : <StarBorder />}
</button>
);
// StarField uses 5 StarButtons to create a field
// with value and onChange props
const StarField = ({ value, onChange }) => (
<>
<StarButton active={value >= 1} onClick={() => onChange(1)} />
<StarButton active={value >= 2} onClick={() => onChange(2)} />
<StarButton active={value >= 3} onClick={() => onChange(3)} />
<StarButton active={value >= 4} onClick={() => onChange(4)} />
<StarButton active={value >= 5} onClick={() => onChange(5)} />
</>
);
export default function App() {
const { control, handleSubmit } = useForm({
defaultValues: {
rating: 0
}
});
const onSubmit = ({ rating }) => {
alert(`Your rating: ${rating}`);
};
return (
<Container>
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
control={control}
name="rating"
render={({ field }) => <StarField {...field} />}
/>
<Button type="submit">Submit</Button>
</form>
</Container>
);
}
结论
使用<Controller/>
一个控制得当的组件,你几乎可以将任何东西变成与 React Hook Form 兼容的表单字段。这个字段可以简单也可以复杂,可以封装任何逻辑,只要它能做到以下两点:
- 接收并呈现字段的当前值/状态,通常通过
value
prop。 - 当该值需要更新时调用一个函数,通常通过
onChange
prop。