具有 propertyname 和 value 参数的通用函数
Generic function with propertyname and value argument
是否可以编写一个带有 2 个参数的通用函数,其中第一个参数是属性名称,第二个是属性值?
这个(不工作的)打字稿显示了我的意图:
declare function add<F extends Record<string, unknown>>(name: keyof F, value: T[path]): void;
add<{a: number; b: boolean}>('a', true); // Should be number
因此,当我使用错误的值参数调用 add 时,出现类型错误。
编辑
我意识到我真正想要的是键入带有重载签名的函数。
declare function add<F extends any>(obj: F, prop: keyof F, value: F[typeof prop]): void;
add({a: 1, b: false}, 'b', 42); // should give compiler error
这是值参数,编译器将其推断为 number | boolean
,因此两者都满足。但是显然,没有办法根据第一个参数来反映类型。
你可以柯里化你的函数:
const add = <
Key extends PropertyKey,
Value,
Obj extends Record<Key, Value>,
Name extends keyof Obj
>(obj: Obj, name: Name, value: Obj[Name]):
Obj[Name] => null as any
const result = add({ a: 42, b: true }, 'a', 42)
add({ a: 1, b: false }, 'b', 42); // error
函数的第一部分:
Key
- 推断对象键
Value
- 推断对象值
Obj
- 推断整个对象
Name
- 推断 name
参数
Obj[Name]
- 我不确定 value
限制。
Here,在我的博客中,您可以找到有关函数参数推断的更多信息
更新
我认为你需要推断 obj
。
这里有一个替代版本:
type Obj = { a: number; b: boolean }
const add = <
Name extends keyof Obj
>(name: Name, value: Obj[Name]):
Obj[Name] => null as any
const result = add('a', 42)
add('b', 42); // error
我已经针对我自己的问题提出了一个通用的解决方案,它不需要运行时函数来解决问题。
它涉及创建两个实用程序类型:UnionToIntersection 和 FuncOverloads。
从后面看,FuncOverloads 生成一个联合类型,其中包含从类型参数反映的所有方法签名:
type FuncOverloads<T> = T extends Record<string, unknown>
? { [P in keyof T]: ((path: P, value: T[P]) => void) }[Exclude<keyof T, symbol>]
: never;
因此它生成了一个如下所示的类型:
Type AddOverloads = FuncOverloads<{a: number; b: boolean}>;
// ((path: "a", value: number) => void) | ((path: "b", value: boolean) => void)
这个函数联合类型,然后得到具体的,当通过UnionToIntersection utility-type:
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
所以我最终得到了一个重载的函数签名:
declare const add: UnionToIntersection<FuncOverloads<{a: number; b: boolean}>>;
add('a', true); // Gives compiler error.
add('a', 42); // All fine
因此 playground 表明,我关于无法处理泛型重载的假设是错误的:)
是否可以编写一个带有 2 个参数的通用函数,其中第一个参数是属性名称,第二个是属性值?
这个(不工作的)打字稿显示了我的意图:
declare function add<F extends Record<string, unknown>>(name: keyof F, value: T[path]): void;
add<{a: number; b: boolean}>('a', true); // Should be number
因此,当我使用错误的值参数调用 add 时,出现类型错误。
编辑 我意识到我真正想要的是键入带有重载签名的函数。
declare function add<F extends any>(obj: F, prop: keyof F, value: F[typeof prop]): void;
add({a: 1, b: false}, 'b', 42); // should give compiler error
这是值参数,编译器将其推断为 number | boolean
,因此两者都满足。但是显然,没有办法根据第一个参数来反映类型。
你可以柯里化你的函数:
const add = <
Key extends PropertyKey,
Value,
Obj extends Record<Key, Value>,
Name extends keyof Obj
>(obj: Obj, name: Name, value: Obj[Name]):
Obj[Name] => null as any
const result = add({ a: 42, b: true }, 'a', 42)
add({ a: 1, b: false }, 'b', 42); // error
函数的第一部分:
Key
- 推断对象键
Value
- 推断对象值
Obj
- 推断整个对象
Name
- 推断 name
参数
Obj[Name]
- 我不确定 value
限制。
Here,在我的博客中,您可以找到有关函数参数推断的更多信息
更新
我认为你需要推断 obj
。
这里有一个替代版本:
type Obj = { a: number; b: boolean }
const add = <
Name extends keyof Obj
>(name: Name, value: Obj[Name]):
Obj[Name] => null as any
const result = add('a', 42)
add('b', 42); // error
我已经针对我自己的问题提出了一个通用的解决方案,它不需要运行时函数来解决问题。 它涉及创建两个实用程序类型:UnionToIntersection 和 FuncOverloads。 从后面看,FuncOverloads 生成一个联合类型,其中包含从类型参数反映的所有方法签名:
type FuncOverloads<T> = T extends Record<string, unknown>
? { [P in keyof T]: ((path: P, value: T[P]) => void) }[Exclude<keyof T, symbol>]
: never;
因此它生成了一个如下所示的类型:
Type AddOverloads = FuncOverloads<{a: number; b: boolean}>;
// ((path: "a", value: number) => void) | ((path: "b", value: boolean) => void)
这个函数联合类型,然后得到具体的,当通过UnionToIntersection utility-type:
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
所以我最终得到了一个重载的函数签名:
declare const add: UnionToIntersection<FuncOverloads<{a: number; b: boolean}>>;
add('a', true); // Gives compiler error.
add('a', 42); // All fine
因此 playground 表明,我关于无法处理泛型重载的假设是错误的:)