【问题标题】:force typescript class method to be unary method with an argument of a specific type强制 typescript 类方法是具有特定类型参数的一元方法
【发布时间】:2019-10-27 10:33:58
【问题描述】:

为了好玩,我想看看我是否可以想出一种方法,只使用类型,它会强制类中的所有函数成为一个一元函数,只要该对象符合指定的接口(在这种情况下,我想确保提供 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

我很好奇是否有办法在类或接口级别做这样的事情(强制方法参数与特定类型签名匹配)?是否可以通过装饰器在单个方法级别完成(此时授予您最好使用适当的类型签名)。

只是测试极限:)

【问题讨论】:

    标签: typescript typescript-typings typescript-generics


    【解决方案1】:

    您只需要为参数的长度添加另一个条件。您可以使用Parameters 将参数的类型提取为元组。元组将具有一个数字字面量类型的长度属性,因此您可以检查 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 '';
        }
    }
    

    【讨论】:

    • 打字王回归!我想知道是否有办法在这里使用infer!没有意识到你可以在长度上做到这一点extends 1!是否可以通过属性对单个方法执行类似操作?
    • @Jordan 我从未离开过 ;-)。我不确定装饰师可能会遇到什么样的天气,我明天会检查。
    • @Jordan 添加装饰器版本
    • ?????????
    • @regevbr 不抱歉,私人成员不会出现在 keyof 中,因此我们无法以这种方式限制私人成员。
    猜你喜欢
    • 2016-09-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-09
    • 1970-01-01
    • 2014-11-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多