利用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表单管理中广受欢迎,几乎可以与其他库争夺第一的位置。

 

试着制作一个简单的表单

画面収録-2022-07-17-6.08.30.gif

表单组件在这里。

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]);
画面収録-2022-07-17-7.08.54.gif

以下是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 的表单)

 

广告
将在 10 秒后关闭
bannerAds