TypeScript 系列:类型更安全的 antd `useForm` 利用泛型

322 阅读2分钟

🤔 问题

今天遇到一个 bug,大概是这样一个 Antd 的搜索表单,点击重置其他下拉框都可以清空,唯独“请选择用户”无法清空。

image.png

Bug 代码如下:

const handleReset = () => {
    history.push({ search: '' });

    form.setFieldValue('modelId', undefined);
    form.setFieldValue('useId', undefined);
    form.setFieldValue('apiKeyId', undefined);

    handleSearch(form.getFieldsValue());
};

🕵️‍♂️ 调试了较长时间,发现是拼写问题 userId ✅ 而非 useId ❌!归根结底是 form.setFieldValue 类型不够安全。为了避免将来再出现类似“低级”问题,重写该函数。

🔧 解决

泛型。即将不支持泛型的 setFieldValue 添加泛型。

// hooks/useForm.ts
import { Form, FormInstance } from 'antd';

export function useForm<Values>() {
  const [form] = Form.useForm<Values>();

  return {
    ...form,
    setFieldValue<K extends keyof Values>(name: K, value: Values[K]) {
      return form.setFieldValue(name as string, value);
    },
  };
}

上述 useFormsetFieldValue 的入参做了强类型校验,如果传入非预期字段将报错,从而规避书写错误。

代码具体说明:

  • 技巧:可以通过 TS 的 keyof T 获取类型 T 的所有 key 组成的联合类型,通过 T[K] 可获取 value 组成的联合类型。
  • useForm 函数虽然没有接受参数,但是接受了泛型 Values,它将被 setFieldValue 的入参使用,将用其 keyvalue 当做入参类型而不是 antd 的 NamePathnumber | string | (string | number)[])从而达到强化其类型的目的。即同时对 keyvalue 都做了强类型约束。

使用:

假设我们表单类型如下

type IFormValues = {
  userId?: number;
  apiKeyId?: number;
  modelId?: number;
};

调用处只需修改一行换成我们实现的 useForm 即可。

// const [form] = Form.useForm<IFormValues>();
const form = useForm<IFormValues>();

现在我们试一试:

form.setFieldValue('useId', undefined); // key TS 类型报错
form.setFieldValue('userId', false); // value 导致 TS 类型报错

类型“"useId"”的参数不能赋给类型“keyof IFormValues”的参数。ts(2345)

image.png

🌌 总结

修复 bug 不是终点,我们需要想着如何通过工具避免才是正道。TS 正是最好用工具之一。

注意本文是 antd v4,v5 应该已经支持泛型了。