利用React 18的Suspense来简化React Hook Form处理异步初始值的操作
首先
我是Taro,平时在创业公司开发面向建筑行业的SaaS。最近我尝试使用React18中的Suspense,并且发现在React Hook Form中,设置表单的初始值变得非常简单,即使对于没有使用过React Hook Form的人也能轻松理解。所以我决定试一试。希望大家能读一读,相信你们会明白的!
这篇文章是关于我们参加的这个活动。
为了满足前提条件,对React Hook Form进行一些复习。
在进入本题之前,我打算对React Hook Form进行一些复习,以便我们都具备相同的前提条件。
(如果你不需要复习,可以直接跳到使用React Hook Form的Suspense部分!)
您可以参考官方文档和其他文章,了解更多关于Suspense的内容。特别推荐uhyo先生的文章,非常易懂且内容完整!
React Hook Form是什么?
React Hook Form是一个以Hooks为基础的表单管理库,在React表单管理中广受欢迎,几乎可以与其他库争夺第一的位置。
试着制作一个简单的表单
表单组件在这里。
import { FC } from "react";
import { useForm } from "react-hook-form";
type Inputs = {
name: string;
age: number;
};
const Form: FC = () => {
const { register, handleSubmit } = useForm<Inputs>();
const onSubmit = (data: Inputs) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("name")} type="text" placeholder="氏名" />
<input {...register("age")} type="number" placeholder="年齢" />
<input type="submit" />
</form>
);
};
在此表单中,有三个地方使用了React Hook Form独特的写法。
调用`useForm`来初始化表单,并接收用于管理表单的方法。
const { register, handleSubmit } = useForm<Inputs>();
使用 React Hook Form 在 register 中注册每个输入。
React Hook Form 使用 name 属性(本例中为 name 和 age)来识别和管理每个输入。
<input {...register("name")} type="text" placeholder="氏名" />
<input {...register("age")} type="number" placeholder="年齢" />
接收React Hook Form中管理的表单数据的handleSubmit。
经过handleSubmit包装的函数(onSubmit)将接收经过验证的表单数据。
const onSubmit = (data: Inputs) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
在表格中设置默认值
下一步,我们将设置表单的初始值。
设置表单的初始值时,您可以在使用 useForm 进行初始化时,将初始值作为参数传递给它。
const { register, handleSubmit } = useForm<Inputs>({
defaultValues: {
name: "taro",
age: 26
}
});
很容易吧。
顺便提一句,您还可以直接为每个输入指定。
<input {...register("name")} type="text" placeholder="氏名" defaultValue="taro" />
<input {...register("age")} type="number" placeholder="年齢" defaultValue={26} />
React Hook Form的复习就到这里了!辛苦了!
在表单的初始值中设置异步数据
在必要的前提条件满足的情况下,我们将考虑关于“如何在React Hook Form中设置异步初始值”的主题。
因此,我们考虑将以下类型的“在2秒后返回姓名和年龄数据的异步函数”的返回值设置为表单的初始值。
const fetchData = async () => {
await new Promise((resolve) => setTimeout(resolve, 2000));
return { name: "taro", age: 26 };
};
暂且直接试试看
首先,我們先從state中直接設定初始值,當取得數據完成後,再將返回值設定給setState。
const [data, setData] = useState<Inputs>();
const { register, handleSubmit } = useForm<Inputs>({ defaultValues: data });
useEffect(() => {
fetchData().then((data) => setData(data));
}, []);
也许有些人能够想象得出来,但是使用这种方法,表单不会反映初始值。
如果向useForm函数传递初始值参数,那么当调用useForm时的data(undefined)将被设置为初始值。
因此,正如官方文档中所述,通常使用reset来设置异步初始值。
使用”reset”方法来设置异步初始值。
重设方法(reset)正如其名,是一个重新初始化表单的函数。当传入参数数据时,它会将该值作为初始值来进行初始化。
所以,在完成数据获取后,我们可以使用reset来再次初始化表单。
const [data, setData] = useState<Inputs>();
const { register, handleSubmit, reset } = useForm<Inputs>();
useEffect(() => {
fetchData().then((data) => setData(data));
}, []);
// データの取得が完了したら再度初期化
useEffect(() => {
reset(data);
}, [reset, data]);
使用這個方法,我們成功地將非同步數據反映為初始值!同樣,您也可以寫出不使用useState的方式來達到這個目的。
const { register, handleSubmit, reset } = useForm<Inputs>();
const resetAsyncForm = useCallback(async () => {
const data = await fetchData();
reset(data);
}, [reset]);
// データの取得が完了したら再度初期化
useEffect(() => {
resetAsyncForm();
}, [resetAsyncForm]);
以下是Component的整体感觉。
const Form: FC = () => {
const { register, handleSubmit, reset } = useForm<Inputs>();
const [loading, setLoading] = useState<boolean>(true);
const resetAsyncForm = useCallback(async () => {
const data = await fetchData();
reset(data);
setLoading(false);
}, [reset]);
// データの取得が完了したら再度初期化
useEffect(() => {
resetAsyncForm();
}, [resetAsyncForm]);
const onSubmit = (data: Inputs) => console.log(data);
if (loading) return <p>Loading...</p>;
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("name")} type="text" placeholder="氏名" />
<input {...register("age")} type="number" placeholder="年齢" />
<input type="submit" />
</form>
);
};
现在变得相当复杂了呢。。。
如果使用Custom Hooks等,也许可以更简单一些,但似乎还是需要改变使用reset进行重新初始化的方法。
此外,对于不了解React Hook Form的人来说,通过reset进行重新初始化的方法似乎不太容易理解,但个人来看,这是一个值得关注的问题。
附带一提,React Hook Form的开发者Bill先生对此方法评论说:“老实说,这可能不是一种美观而干净的方法…”。
为了解决这个问题,我这次尝试了一下Suspense!
使用React Hook Form时,使用Suspense
让我们使用Suspense吧!
通过使用Suspense,我们可以将组件本身设为Loading状态,而不是在组件中添加Loading状态。
因此,在组件内部不再需要意识到是否正在加载,也不再需要将异步数据获取视为副作用。
当我想起在使用 useForm 进行初始化时,最初进行重置的原因是因为数据尚未被获取到的时机,我觉得或许可以做得更好…!
我们来试试看吧!
使用悬念来设置异步的初始值
为了使用悬念,定义一个全局变量来接收初始值。
(虽然可以使用state来处理,但由于这篇文章中uhyo先生提到的原因稍微复杂一些,所以使用全局变量)。
let data: Inputs | undefined;
接下来,根据悬疑小说的惯例,我们将引发一个Promise!并且,如果Promise成功,将其返回值赋给初始值。
if (data === undefined) {
throw fetchData().then((d) => (data = d));
}
然后请使用Suspense将表单的Component包围起来,并将加载时的显示传递到fallback中。
<Suspense fallback={<p>Loading...</p>}>
<Form />
</Suspense>
当最终从表单组件中删除不再需要的处理时,结果如下所示。
const Form: FC = () => {
if (data === undefined) {
throw fetchData().then((d) => (data = d));
}
const { register, handleSubmit } = useForm<Inputs>({ defaultValues: data });
const onSubmit = (data: Inputs) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("name")} type="text" placeholder="氏名" />
<input {...register("age")} type="number" placeholder="年齢" />
<input type="submit" />
</form>
);
};
重设(reset)和 useEffect 不存在了,情况好了很多!如果将数据获取分离到定制钩子(Custom Hooks)中,会更加简洁。
// データの取得をCustomHooksに切り出す
const useData = () => {
if (data === undefined) {
throw fetchData().then((d) => (data = d));
}
return data;
};
const Form: FC = () => {
const { register, handleSubmit } = useForm<Inputs>({
defaultValues: useData()
});
const onSubmit = (data: Inputs) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("name")} type="text" placeholder="氏名" />
<input {...register("age")} type="number" placeholder="年齢" />
<input type="submit" />
</form>
);
};
我觉得这样即使是不了解React Hook Form的人看过也能大致猜测出它在做什么,所以这次就到这里算是完成了!我把实际创建的表单放在了CodeSandbox上,如果方便的话请随意尝试。
給你一個額外的小禮物
只是给你一点小额外的东西。
使用SWR的示例
虽然SWR仍然是一项实验性功能,但因为有悬念模式(Suspense Mode),因此可以轻松支持悬念。
const Form: FC = () => {
const { data } = useSWR("data", fetchData, { suspense: true });
const { register, handleSubmit } = useForm<Inputs>({ defaultValues: data });
const onSubmit = (data: Inputs) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("name")} type="text" placeholder="氏名" />
<input {...register("age")} type="number" placeholder="年齢" />
<input type="submit" />
</form>
);
};
对于Suspense Mode,仍然存在一些未完成的地方,例如数据类型中包含了undefined。因此,我认为目前在实际工作中使用可能还为时过早。关于在Suspense Mode中的类型支持问题,可以在这个问题讨论中找到更多相关内容。
总结
这次我尝试使用React18中引入的Suspense来设置React Hook Form的异步初始值!个人认为,由于表单是用户经常操作的UI,所以React18中引入的各种功能会变得非常有用!也很期待React Hook Form未来的发展!另外,平时我会在Zenn上发布文章,或在Twitter上发技术相关的推文,如果你愿意,可以关注我或者跟我互动,这会让我非常高兴!
如果对文章的内容有任何感想、建议或问题,请务必留言告诉我们!非常感谢您读到最后!
感谢你的支持和帮助。
悬念
React Hooks Form (使用 React Hooks 的表单)