假设我有两种类型:
type PathBare<T> = keyof T & string
type PathWrapped<T> = `${keyof T & string}`
两者都只是作为一个联合体获取对象的字符串键.但是一个人在字符串字面量中插入字符串,另一个人不这样做.
它们应该是等价的,因为字符串文字的包装不应该在 struct 上改变类型.例如:
type ABC = 'a' | 'b' | 'c'
type A = ABC extends `${ABC}` ? true : false // true
但是,当我向这些类型传递泛型类型而不是具体类型时,发生了一些奇怪的事情:
type Named = { name: string, foo: string }
function foo<T extends Named>(arg: T): T {
const a1: PathWrapped<Named> = 'name' // fine
const a2: PathBare<Named> = 'name' // fine
const b1: PathWrapped<T> = 'name' // Type 'string' is not assignable to type '`${keyof T & string}`'.(2322)
const b2: PathBare<T> = 'name' // fine
return arg
}
这里使用T
应该与使用约束T
的类型完全相同,但事实并非如此.它似乎完全忘记了它是什么,在没有强制转换的情况下不再是可赋值的.
为什么会这样呢?这里发生了什么事?
这显然是一个人为的例子.库中的实际用例为虚线路径提供了一种类型,用于通过字符串标识嵌套字段.我已经减少了涉及的类型,以找到导致问题的最小情况.
鼓舞人心的例子:
import { useEffect } from 'react'
import { useForm } from 'react-hook-form'
type Named = { name: string }
function useFormWithNamedData<T extends Named>(data: T) {
const { setValue } = useForm({ defaultValues: async () => data })
useEffect(() => {
setValue('name', 'foo') // Argument of type '"name"' is not assignable to parameter of type 'Path<T>'.(2345)
}, [])
return {setValue}
}
useFormWithNamedData({ name: 'a', foo: 'b' }).setValue('foo', 'c') // good
function useFormWithNamedDataNonGeneric(data: Named) {
const { setValue } = useForm({ defaultValues: async () => data })
useEffect(() => {
setValue('name', 'foo') // fine
}, [])
return {setValue}
}
useFormWithNamedDataNonGeneric({ name: 'a', foo: 'b' }).setValue('foo', 'c') // error, needs to work
这里,react-hook-form
中的setValue
函数期望设置属性的字符串路径.但'name'
是不允许的,即使'name'
是约束的一部分,因此它必须存在.看来编译器应该知道这一点.
我不能在调用useForm
时直接使用约束,因为我想返回依赖于T
的类型的数据.在这种情况下,setValue
将接受'name'
以及传入该对象的任何其他路径.
在试图找出原因的过程中,我把这个问题简化为这个问题的顶部.