为了好玩,我想看看是否可以提出一种仅使用类型的方法,该方法将迫使类中的所有函数成为一元函数,只要该对象符合指定的接口,它就可以接受任何对象(在这种情况下,我想确保提供了ContextObj
作为参数)
我像这样刺了一下
interface BaseContext {}
interface SomeContext { foo: 'lish'; };
interface ContextObj<T> { context: T }
// a unary function that takes an argument that conforms to the ContextObj interface
// and returns a value of type U
type ContextFn<T, U> = (x: ContextObj<T>) => U;
// a type to represent any function
type AnyFunction = (...args: any[]) => any;
// we use mapped types and conditional types here to determine if
// T[K], the value of the property K on type T, is a function
// if it's not a function we return the T[K] unaltered
// if it is a function we make sure it conforms to our ContextFn type
// otherwise we return never which I was hoping would result in an error
type ContextService<T, U extends BaseContext> = {
[K in keyof T]: T[K] extends AnyFunction
? T[K] extends ContextFn<U, ReturnType<T[K]>>
? T[K]
: never
: T[K]
};
class BarService implements ContextService<BarService, SomeContext> {
test:string = 'test';
// expected error: not assignable to type never
updateBar(): string {
return '';
}
}
事实证明,此操作不符合预期,因为它是TypeScript的限制(或功能?):https://github.com/Microsoft/TypeScript/wiki/FAQ#why-are-functions-with-fewer-parameters-assignable-to-functions-that-take-more-parameters
我很好奇,是否有一种方法可以在类或接口级别上执行类似的操作(强制方法参数匹配特定的类型签名)?是否可以通过装饰器在单个方法级别完成操作(在此点上,您还可以只使用适当的类型签名)。
只需测试一下限制:)
答案 0 :(得分:1)
您只需要为参数的长度添加另一个条件。您可以使用Parameters
将参数的类型提取为元组。元组将具有数字文字类型的length属性,因此您可以检查length
是否扩展了1
:
type ContextService<T, U extends BaseContext> = {
[K in keyof T]: T[K] extends AnyFunction ?
T[K] extends ContextFn<U, ReturnType<T[K]>> ?
Parameters<T[K]>["length"] extends 1 ? T[K]
: never : never
: T[K]
};
class BarService implements ContextService<BarService, SomeContext> {
test: string = 'test';
// error: not assignable to type never
updateBar(): string {
return '';
}
updateBar2(p: ContextObj<SomeContext>): string { // ok
return '';
}
}
对于提示用户该问题可能很有用,尽管不支持自定义错误,但这非常接近:
type ContextService<T, U extends BaseContext> = {
[K in keyof T]: T[K] extends AnyFunction ?
T[K] extends ContextFn<U, ReturnType<T[K]>> ?
Parameters<T[K]>["length"] extends 1 ? T[K]
: ["Method must have exactly one parameter of type ", ContextObj<U>, "Found Parameters:", Parameters<T[K]>]
: ["Parameters types not macthed, expected [", ContextObj<U>, "] Found Parameters:", Parameters<T[K]>]
: T[K]
};
class BarService implements ContextService<BarService, SomeContext> {
test: string = 'test';
// error: not assignable to type never
updateBar(): string {// Type '() => string' is missing the following properties from type '["Method must have exactly one parameter of type ", ContextObj<SomeContext>, "Found Parameters:", []]'
return '';
}
updateBar2(p: ContextObj<SomeContext>): string {
return '';
}
updateBar3(p: SomeContext): string { // Type '(p: SomeContext) => string' is missing the following properties from type '["Parameters types not macthed, expected [", ContextObj<SomeContext>, "] Found Parameters:", [SomeContext]]'
return '';
}
}
修改
回答评论问题:这可以用装饰器完成吗?
答案是肯定的,我们可以捕获装饰器所应用的类以及它所应用的键作为类型参数,并使用与之前对属性键相同的逻辑。只是这次我们将错误附加到传递给装饰器的键上:
type Check<TSig extends (...a: any[]) => any, T, K extends keyof T> =
T[K] extends (...a: any[]) => any ?
T[K] extends TSig ?
Parameters<T[K]>["length"] extends 1 ? unknown
: ["Method must have exactly one parameter of type ", Parameters<TSig>, "Found Parameters:", Parameters<T[K]>]
: ["Parameters types not macthed, expected [", Parameters<TSig>, "] Found Parameters:", Parameters<T[K]>]
: unknown
function ensureSignatire<TSig extends (...a: any[]) => any>() {
return function <TTarget, TKey extends keyof TTarget>(target: TTarget, key: TKey & Check<TSig, TTarget, TKey>) {
}
}
class BarService {
test: string = 'test';
@ensureSignatire<ContextFn<SomeContext, any>>() // Type '"updateBar"' is not assignable to type '["Method must have exactly one parameter of type ", [ContextObj<SomeContext>], "Found Parameters:", []]'.
updateBar(): string {
return '';
}
@ensureSignatire<ContextFn<SomeContext, any>>() //ok
updateBar2(p: ContextObj<SomeContext>): string {
return '';
}
@ensureSignatire<ContextFn<SomeContext, any>>() // Type '"updateBar3"' is not assignable to type '["Parameters types not macthed, expected [", [ContextObj<SomeContext>], "] Found Parameters:", [SomeContext]]'
updateBar3(p: SomeContext): string {
return '';
}
}