简体   繁体   中英

typescript can I make this code more DRY?

I have a class of functions that I use as decorators:

interface SchemaDefinition {
    type: any;
    label?: string | Function;
    optional?: boolean | Function;
    min?: number | boolean | Date | Function;
    max?: number | boolean | Date | Function;
    minCount?: number | Function;
    maxCount?: number | Function;
    allowedValues?: any[] | Function;
    decimal?: boolean;
    exclusiveMax?: boolean;
    exclusiveMin?: boolean;
    regEx?: RegExp | RegExp[];
    custom?: Function;
    blackbox?: boolean;
    autoValue?: Function;
    defaultValue?: any;
    trim?: boolean;
}

class Decorators {
    static Type(value: Pick<SchemaDefinition, 'type'>) {
        return SchemaDecorators.extendSchema('type', value)
    }

    static Min(value: Pick<SchemaDefinition, 'min'>) {
        return SchemaDecorators.extendSchema('min', value)
    }

    static Max(value: Pick<SchemaDefinition, 'max'>) {
        return SchemaDecorators.extendSchema('max', value)
    }

    ....
}

This does not seem very DRY. Can it be improved?

TypeScript doesn't always make DRY very easy, especially since all type information is erased at runtime. That means sometimes the only way not to repeat yourself is to make a dummy runtime object whose type information can be inferred by the compiler.

Here's an attempt at being DRY, which you might decide is not worth it. First, we will need to loop over the keys of SchemaDefinition at runtime to create Decorators programmatically. I create a SchemaDefinitionClass with dummy undefined values so that we can get at those keys:

const ω: never = (void 0)!;
class SchemaDefinitionClass {
    type: any = ω;
    label?: string | Function = ω;
    optional?: boolean | Function = ω;
    min?: number | boolean | Date | Function = ω;
    max?: number | boolean | Date | Function = ω;
    minCount?: number | Function = ω;
    maxCount?: number | Function = ω;
    allowedValues?: any[] | Function = ω;
    decimal?: boolean = ω;
    exclusiveMax?: boolean = ω;
    exclusiveMin?: boolean = ω;
    regEx?: RegExp | RegExp[] = ω;
    custom?: Function = ω;
    blackbox?: boolean = ω;
    autoValue?: Function = ω;
    defaultValue?: any = ω;
    trim?: boolean = ω;
}
interface SchemaDefinition extends SchemaDefinitionClass { }

You can verify that SchemaDefinition is defined as before. I guess setting all the values explicitly to undefined is repeating yourself, but at least it's a short repetition.

Oh, I don't know what SchemaDecorators.extendSchema() returns. I will make a stub return type for it; change it as you want:

type Foo<K,V> = {
  foo: K;
  bar: V;
}
declare const SchemaDecorators: {
  extendSchema<K extends string, V>(k: K, v: V): Foo<K, V>;
}

Now instead of making Decorators a class with a bunch of static methods, I will make it an object with a bunch of method properties. It mostly amounts to the same thing and can be merged with a class if you need it.

const Decorators: {
  [K in keyof SchemaDefinition]: (value: Pick<SchemaDefinition, K>) => Foo<SchemaDefinition, Pick<SchemaDefinition, K>>
} = {} as any;

Note! The above definition means that the static methods are lowercase with the same name as the keys of SchemaDefinition , not CamelCase. That's unavoidable unless you want to write out a mapping like {type: 'Type', ...} which I don't.

Okay, let's populate Decorators :

Object.keys(new SchemaDefinitionClass()).forEach(<K extends keyof SchemaDefinition>(k: K) => { 
  Decorators[k] = ((value: Pick<SchemaDefinition, K>) => SchemaDecorators.extendSchema(k, value)) as any;
});

So I think that will work for you, but it's pretty ugly. You might be happier just repeating yourself. It's up to you. Hope that helps! Good luck!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM