表格每天都使用,登录/注册,订购某物时填写信息,...它确实是网站的杰作。
我开始用Redux Form
在 React 中制作表单,该使用Redux
存储有关表单的信息。是的,这是我们使用Redux
做所有事物的旧时。
如今,情况发生了变化。我们有多个库:Formik
,React Final Form
,React Hook Form
,...大多数时候使用 react state 存储信息。
我知道, remix 之类的框架鼓励我们使用纯HTML来制作形式。但是通常,如果我们想要一个良好的用户体验,我们必须使用客户库快速反馈,或者当您需要在字段上取决于彼此的复杂验证时。
React Hook Form 是一个专注于性能的库。查看其实现真的很有趣,可以学习一些可以在其他情况下可以使用的模式。
让我们看一下与其他表单库实现相比,它使其与众不同的是什么。
先决条件
在开始谈论实施之前,我想定义一些术语将全部放在同一页面上:
- 字段:从用户收集数据的元素(输入,选择,datepicket,...)。
- 字段名称:字段的标识符。
- 字段值:用户填充的值。
什么是“简单”的实现?
如果今天,我必须进行表单实施。本能地,我会使用 state 来制作像Formik
或React Final Form
这样的人:
function MyForm() {
const [values, setValues] = useState({
firstname: "",
lastname: "",
});
const onChange = (fieldName, fieldValue) => {
setValues((prevValues) => ({
...prevValues,
[fieldName]: fieldValue,
}));
};
return (
<form
onSubmit={() => {
// Do something with the form values
// that are in the `values` variable
}}
>
<label>
Firstname
<input
type="text"
name="firstname"
value={values["firstname"]}
onChange={(e) => onChange(e.target.value)}
/>
</label>
<label>
Lastname
<input
type="text"
name="lastname"
value={values["lastname"]}
onChange={(e) => onChange(e.target.value)}
/>
</label>
<button type="submit">Submit</button>
</form>
);
}
这里没有什么花哨的。我只是将用户填充的值存储在React状态下。我们走了。
这是一个真正简化的实现。在现实生活中,我可能会使用还原器,因为我想存储更多的值:验证错误,知道表单是否在提交,是否脏,...
如果您想查看更现实的实现
// I do not handle validation and form states
// but if I do I will probably use a reducer to that
// instead of multiple states
function useForm(initialValues = {}) {
const [values, setValues] = useState(initialValues);
const handleSubmit = (onSubmit) => (e) => {
e.preventDefault();
onSubmit(values);
};
const register = (fieldName) => {
return {
value: values[fieldName],
onChange: (event) => {
setValues((prevValues) => ({
...prevValues,
[fieldName]: fieldValue,
}));
},
};
};
return {
register,
handleSubmit,
};
}
function MyForm() {
const { values, onChange, handleSubmit } = useForm({
firstname: "",
lastname: "",
});
return (
<form
onSubmit={() => {
// Do something with the form values
// that are in the `values` variable
}}
>
<label>
Firstname
<input
type="text"
name="firstname"
value={values["firstname"]}
onChange={(e) => onChange(e.target.value)}
/>
</label>
<label>
Lastname
<input
type="text"
name="lastname"
value={values["lastname"]}
onChange={(e) => onChange(e.target.value)}
/>
</label>
<button type="submit">Submit</button>
</form>
);
}
你知道吗?那不是实现 React hook表格的方式。
React Hook表单实现的关键点
不要使用反应状态
要知道的主要内容是,该库不使用React状态 /还原器来存储数据,而是使用引用。
它使用 React Ref 的懒惰初始化:
function useForm(config) {
const formControl = useRef(undefined);
// Lazy initialization of the React ref
// Enter the condition only at the first render
// (`createFormControl` returns an object
if (formControl.current === undefined) {
formControl.current = createFormControl(config);
}
}
然后在createFormControl
中,所有内容都存储在const
中,它们被突变:
function createFormControl({ initialValues }) {
const formValues = initialValues;
const onChange = (fieldName, fieldValue) => {
formValues[fieldName] = fieldValue;
};
return {
onChange,
};
}
现在,它的速度非常快,因为不再渲染。
mmmm等等,不再渲染了吗?我们怎么知道何时变化和形式状态?
让我们看看。
观察者模式
这种模式在行业中真正使用:react-query
,react-redux
,...使用它。
原理真的很简单,但如此强大。
我们有:
- a
subject
:这是一个跟踪实体变化并通知此更改的对象 -
observers
:他们通过订阅主题 来聆听实体的变化。
如果您想查看实现
function createSubject() {
const listeners = [];
const subscribe = (listener) => {
// Add the listener
listeners.push(listener);
// Return an unsubscribe method
return () => {
listeners = listeners.filter((l) => l !== listener);
};
};
const update = (value) => {
for (const listener of listeners) {
listener(value);
}
};
return {
subscribe,
update,
};
}
React Hook Form 有3个主题:
-
watch
:跟踪字段值的变化 -
array
:跟踪字段数组值的更改 -
state
:跟踪表单状态的变化
现在,useWatch
钩订阅了watch
主题,并在我们想跟踪已更改的字段时更新React state
。
,在需要时,我们在这里进行组件。
等等!当我想在表单变脏时被通知时,当其他状态值更改时,我的组件不会重新渲染。怎么可能?
这是下一个关键点。
代理 / DefineProperty
如果您不知道什么是代理,您可以阅读我的文章Proxy in JS: what the hell?。
在RHF中,代理用于知道状态的哪些特性用于组件。
感谢他们,我们可以知道组件会聆听哪些属性,并且仅在这些属性发生变化时才渲染。
function createProxy(formState, listenedStateProps) {
const result = {};
// Loop on the property which are in the form state
for (const propertyName in formState) {
Object.defineProperty(result, {
get() {
// Keep in mind that the property is listened
listenedStateProps[propertyName] = true;
// And returns the actual value
return formState[propertyName];
},
});
}
return result;
}
,因此,观察者模式我们可以在更改式状态属性时更新组件。
// control is an object that has all the logic
// and the mutated object like `_formValues`,
// `_formState`, `_subjects`, ...
function useFormState(control) {
// At start nothing is listened
// In reality there are more properties
const listenedStateProps = useRef({
isDirty: false,
isValid: false,
});
// Initialize with the current `_formState` which is
// mutated
const [formState, setFormState] = useState(
control._formState
);
useEffect(() => {
return control._subjects.state.subscribe(
([stateProp, stateValue]) => {
// If the changed property is listened let's update
if (listenedStateProps.current[stateProp]) {
setState((prev) => ({
...prev,
[stateProp]: stateValue,
}));
}
}
);
}, [control._subjects]);
return createProxy(formState, listenedStateProps);
}
稳定的事件听众没有过时的外部数据
另一种策略是,在事件侦听器中使用的值的参考用途,这些值要多于useCallback
或useEffect
中使用。
为什么?
因为我们不想在回调中有过时的数据,因此我们必须在useCallback
的依赖性中添加它。因此,每当依赖性正在改变时,它都会创建一个全新的参考,因为这是事件侦听器。
注意:它实际上在每个渲染上创建了一个新的参考
而不是:
function MyComponent({ someData }) {
// The reference of showData is not stable!
const showData = useCallback(() => {
console.log("The data is", someData);
}, [someData]);
return (
<MemoizedButton type="button" onClick={showData}>
Show the data, please
</MemoizedButton>
);
}
我们有:
function MyComponent({ someData }) {
const someDataRef = useRef(someData);
useLayoutEffect(() => {
// Keep the reference up-to-date
someDataRef.current = someData;
});
// The reference of showData is now stable!
const showData = useCallback(() => {
console.log("The data is", someDataRef.current);
}, []);
return (
<MemoizedButton type="button" onClick={showData}>
Show the data, please
</MemoizedButton>
);
}
如果您已经有我的文章useEvent: the new upcoming hook?,则可能已经注意到这是相同的原则。不幸的是,useEvent
不会很快到来,所以我们必须在项目中更长一点。
实际上,在 react hook形式代码库中实现不相同。 REF直接在渲染中更新,但我不建议您使用它,因为可能会因新的并发功能造成一些麻烦,并且在组件中存在不一致。 这是相同的模式,而So想要的koude24 hook最终不会出来:(补充信息
function MyComponent({ someData }) {
const someDataRef = useRef(someData);
// Do not update directly in the render!!!
someDataRef.current = someData;
// But use a `useLayoutEffect`
useLayoutEffect(() => {
someDataRef.current = someData;
});
}
结论
您应该更舒适地浏览 React Hook Form 并了解代码。
某些要点可以在您自己的代码库中或要开发库中。
注意不要过于优化您的代码。如果您想将相同的模式与突变应用相同,我建议每次数据发生变化,而不试图在认为不是必需的情况下不进行突变,因为当组件有条件地呈现时可能会给您带来麻烦。例如,我会防止这种仅在当前渲染上使用formState.isDirty
时突变形式状态的kind of code,但是当您在下一个渲染时侦听状态肮脏时不起作用。
请随时发表评论,如果您想看到更多,可以在Twitch上关注我或转到我的Website。如果you want to buy me a coffee
,这里有一个小链接