reactjs 如何从传递的参数派生类型?

xfyts7mz  于 2023-01-04  发布在  React
关注(0)|答案(1)|浏览(120)

我有一个带有methods参数的组件,它的类型定义来自一个导入的库。

import { useForm } from "react-hook-form";

interface FormParams  {
  commonField: string
  optionalField1: string
  optionalField2: number
}

function App() {
  const methods = useForm<FormParams >();
  const { register, handleSubmit, watch } = methods

  useCustomHook({ methods, optionalFieldName: 'optionalField2' })
}

在这个组件中,我调用了一个函数,该函数需要这个methods参数和一个可选表单参数的名称作为参数。
这是这个函数的实现

import { FieldPath, FieldValues, useForm, UseFormReturn } from "react-hook-form";

type CustomFieldValues = FieldValues & {
  commonField: string
}

type HookParams = {
  methods: UseFormReturn<CustomFieldValues>
  optionalFieldName?: FieldPath<CustomFieldValues>
}

const useCustomHook = ({ methods, optionalFieldName = 'optionalField1' }: HookParams) => {
  const { watch } = methods
  const fieldValue = watch(optionalFieldName) // ❌ returns with type `any` because of wrong typing
  const commonFieldValue = watch('commonField') // ✅ returns with correct `string` type

  // do some stuff
}

函数的类型不完整。在此处,watch函数返回给定窗体参数名称的当前值。当前,commonFieldValue的类型正确地为string,但fieldValue的类型正确地为any
如果我可以用UseFormReturn<FormParams>来表示methods,我就不会有这个问题了,但是这个函数实际上是从很多有不同表单参数的组件中调用的,唯一一个公共的参数是commonField,而其他字段是基于传递给函数的参数。这段代码可能看起来毫无用处,但我试图尽可能地简化它,以专注于问题。
所以如果我用

useCustomHook({ methods, optionalFieldName: 'optionalField2' })

fieldValue参数应键入为number

const fieldValue = watch(optionalFieldName)

如果我用调用函数

useCustomHook({ methods })

fieldValue参数应键入为string(因为默认值为optionalField1)。

const fieldValue = watch(optionalFieldName)

我们可以为HookParams定义一个泛型类型吗?这样我就可以在函数内部得到正确的类型定义。
下面是针对该问题的sandbox

3qpi33ja

3qpi33ja1#

你不可能做到100%类型安全,UseFormReturn的方式是不变的(所以UseFormReturn<CustomFieldValues>UseFormReturn<FormParams>之间没有继承关系,请看我关于变量here的讨论)
然而,当涉及到重载时,你可以使用更宽松的可赋值性检查。因此,我们将有一个公共函数签名,允许我们传入派生类型和一个实现签名,将使用UseFormReturn<CustomFieldValues>。现在我想强调的是,这将不是100%类型安全的,你可能能够做你应该被允许的事情,但这是它将得到的最好结果

function useCustomHook<T extends CustomFieldValues>(args: HookParams<T>): void
function useCustomHook({ methods, optionalFieldName = 'optionalField1' }: HookParams<CustomFieldValues & Record<string, unknown>>) {
  const { watch } = methods
  const fieldValue = watch(optionalFieldName) // We don't know what type the field will have so we get unknown (from & Record<string, unknown>)
  const commonFieldValue = watch('commonField') // ✅ returns with correct `string` type
}

Playground链接
注:在实现签名上我还必须添加Record<string, unknown>。这是为了允许我们用一个我们不知道的任意属性进行索引。这意味着你可以用任何字符串调用watch,但是你会得到unknown,如果你用一个已知属性调用watch,你会得到正确的类型。

相关问题