在提出第4版Hooked-Form时,我提醒自己我对Hooked-Form的目标是什么:
- 捆束尺寸小
- 开箱即用的高性能
- 良好的开发人员经验
在版本3中,这些是通过一种或另一种方式实现的,但是我知道这会更好,因此我退后一步,研究了可能的方式。
在第一部分中,我将讨论一个较小的示例中Hooked-Form的工作方式,在以下部分中,我将讨论如何尝试改进该库的目标。
它是如何工作的
我们不必重新发明轮子,在redux-form中使用的Form-Field方法是非常好的方法,并且可以很好地扩展。该方法的基本原理一直保留下来,但始终以尽可能减小束的大小为思路。
让我们用Hooked-Form做一个最小的例子。假设我们有一个组件,您可以在其中编辑您的姓名和朋友。我们的表单将具有一组初始值,我们可以提交它。
const Wrapper = ({ children, name, friends }) => {
const initialValues = React.useMemo(() => ({
name: props.name,
friends: props.friends,
}, [name, friends]);
return (
<HookedForm onSubmit={console.log} initialValues={initialValues}>
{children}
</HookedForm>
)
}
这就是您所需要的,所有选项都可以在这里找到。在<HookedForm>
将一个form
标签为您在引擎盖下,绑定就可以了的onsubmit。您可能会想,但是如果我想传递额外的属性怎么办?好吧,任何传递的不是选项的属性HookedForm
都将绑定到form
标签,这使您可以提供例如className
。
让我们做一个,TextField
以便我们可以在表单中更改我们的名称。
const TextField = ({ fieldId }) => {
const [{ onChange }, { value }] = useField(fieldId);
return <input onChange={e => onChange(e.target.value)} value={value} />
}
useField
包含更多类似onBlur
,…来管理字段的状态。该字段不会对您是否在web
环境中做出任何假设,因此可以在react-native
…中使用它。
如果我们想用自己的名字来命名,那我们就要做<TextField fieldId="name" />
,我们很好!
在这里阅读有关此钩子的更多信息
如果我们想管理我们的friends
领域useFieldArray
,我们将为您服务。
const Friends = () => {
const [{ add }, { value: friends }] = useFieldArray('friends');
return (
<React.Fragment>
{friends.map((friend, index) => (
<div>
<TextField fieldId={`friends[${i}].name`} />
<button onClick={() => remove(i)}>Unfriend</button>
</div>
)}
<button onClick={() => add({ id: friends.length })}>Add friend</button>
</React.Fragment>
)
}
在这里阅读有关此钩子的更多信息
所有这一切都应该由您来设置,以管理您的朋友和您自己的名字,您可以在此处查看该示例。
开发者经验
我们有一个非常著名的方法,即用于受控字段的Form-Field方法,该方法工作得很好并且感觉非常直观。我们在一个中心位置控制我们的状态,Form
并通过React.contextProvider
。一个字段可以选择加入某个特定的字段,并针对该特定字段挂钩错误。
我意识到,在某些情况下,您想对另一个字段中的更改做出反应,并针对此更改当前或可能的值。在v4之前,必须添加另一个useField
在该字段useFormConnect
上侦听的侦听器,甚至添加一个侦听整个表单状态并手动检查所有内容的侦听器。
幸运的是,在v4中,我们有一个解决方案,称为useSpy
。
您可以在此处阅读有关useFormConnect的更多信息。
让我们看一个例子:
import { useField, useSpy } from 'hooked-form';
const optionsForAMinor = [...];
const regularOptions = [...];
const MySelect = () => {
const [options, setOptions] = useState(optionsForAMinor);
const [{ setFieldValue }, { value }] = useField('selectField');
useSpy('age', (newAge) => {
if (newAge >= 18) {
setOptions(regularOptions);
} else {
setOptions(optionsForAMinor);
}
});
return <Select options={options} value={value} onChange={setFieldValue} />
}
每当我们的年龄改变时,我们都可以更改选项,而不必useField
在一个字段中混合使用多个钩子。
尺寸+性能
在此之前,当值更改时,Provider
它将检查需要更新哪些钩子,并从Provider
更新的React版本中的钩子中进行更新,这将触发一条console.warn
说法,即父母无法更新孩子。
这使我重新考虑如何处理组件的传播更新,我们使用中calculateChangedBits
提供的React.createContext
表示我们永远不想处理转售,因此其价值变为() => 0
。如果您不熟悉此API,请在此处阅读更多信息。
这意味着对上下文值的更新将永远不会触发任何渲染,这不是我们想要的,但是它提高了性能,因为在正常的上下文情况下,useContext
即使更改的部分与它们无关,它也会触发每个渲染。
下一步是制作一个小的事件发射器,该事件发射器将在每个字段上注册。我们有一个“主题”,我们可以在每个字段上以的形式收听fieldId
,这应该绰绰有余。
每个人都useField
将使用fieldId
提供的参数将自己注册到发射器。当错误触发更改时,…它将查看已更改的部件并发出fieldIds
引起这些挂钩的渲染的相关信息。
这种紧凑的发射器减少了200Bytes的大小。
总结
我希望我成功改善了开发人员体验,性能和尺寸方面对我来说似乎有所改善。
如果您喜欢图书馆,请别忘了⭐️仓库,这意味着很多!
让我知道您在评论中的想法或发给我!
useSpy的奖励示例:https ://codesandbox.io/s/admiring-vaughan-u2lzt