Typescript Fluent API - utelate funksjoner fra returverdien til hver funksjon


Dette spørsmålet bygger på spørsmålet og det aksepterte svaret som finnes i denne lenken.

Jeg vil skrive en klasse for skjemaoppretting, der en underliggende type T brukes til å bestemme hvilke funksjoner som kan kalles. Som et eksempel, si at dette er modellen vi ønsker et skjema for:

interface MyFormModel {
    name: string;
    options: number[];
    isPrimary: boolean;
    address: {
        street: string;
        houseNumber: number;
        someArrayThatsRelevantToAddresses: string[];
    }
}

Jeg vil kunne lage en avkrysningsboks for egenskapen 'isPrimary', og deretter vil jeg at den returnerte verdien av this ikke skal inkludere metoden som ble brukt for å lage avkrysningsbokser, siden det ikke finnes flere boolske egenskaper igjen å kalle "createCheckbox" for. Jeg holder oversikt over hvilke egenskaper som allerede er brukt ved å bruke en andre typeparameter K. Etter hvert funksjonskall med en nøkkel fra T, oppdateres K slik at vi kan spore hvilke nøkler som allerede er brukt. Her er min implementering:

// T is the model for the form, K will accumulate props from T as we build the form
class FormBuilder<T, K extends Partial<T> = {}> {

    buildCheckbox<NewKey extends BooleanKeysOf<T>>(): FormBuilder<T, AddProp<T, K, NewKey>> {
        return this as any;
    }

    buildBlock<NewKey extends CompoundKeysOf<T>>(key: NewKey): FormBuilder<T, AddProp<T, K, NewKey>> {
        return this as any;
    }

    buildInput<NewKey extends StringOrNumberKeysOf<T>>(): FormBuilder<T, AddProp<T, K, NewKey>> {
        return this as any;
    }

    buildArray<NewKey extends ArrayKeysOf<T>>(): FormBuilder<T, AddProp<T, K, NewKey>> {
        return this as any;
    }

    finishForm(): K extends T ? FormForModel<T> : MissingProps<T, K> { // tells us which props are missing!
        // use childforms to build a complete form
        return 'hello' as any;
    }

    // Get a FormBuilder that accumulates props to build one of the object properties of the parent form
    getChildBuilder<NewKey extends CompoundKeysOf<T>>(): FormBuilder<T[NewKey]> {
        return new FormBuilder();
    }
}

Dette bruker noen hjelpertyper:

/**
 * Utility types
 */

// Get from Type T all of the keys that have values of type SomeType
type KeysOfType<T, SomeType> = { [K in keyof T]: T[K] extends SomeType ? K : never }[keyof T]

// Get all keys that are boolean, or string | number, or arrays, or other objects
type BooleanKeysOf<T> = KeysOfType<T, boolean>;
type StringOrNumberKeysOf<T> = KeysOfType<T, string | number>;
type ArrayKeysOf<T> = KeysOfType<T, any[]>;
type CompoundKeysOf<T> = Exclude<KeysOfType<T, object>, ArrayKeysOf<T>>; // get all props that have object types, except arrays

// a type representing the form we're building
type FormForModel<T> = { [K in keyof T]: {} } };

// Add to K another prop that's a part of T
type AddProp<T, K extends Partial<T>, NewKey extends keyof T> = K & { [S in NewKey]: T[S] };

// Which props are missing from K to fill out T?
type MissingProps<T, K extends Partial<T>> = Omit<T, keyof K>;

Ved å bruke denne tilnærmingen er det ganske enkelt å begrense typeparameteren til hver funksjon til kun nøkler som ikke har blitt kalt før, inntil kall-signaturen blir NewKey extends never og ingen kall kan gjøres.

Jeg vil imidlertid gjerne fjerne en funksjon helt fra typens signatur når den ikke har noen lovlige verdier å kalles med. Jeg forestiller meg noe som dette:

    buildCheckbox<NewKey extends Exclude<BooleanKeysOf<T>, keyof K>>(): MaybeExcludeCheckbox<T, K, NewKey> {
        return this as any;
    }

type MaybeExcludeCheckbox<T, K extends Partial<T>, NewKey extends keyof T> = Exclude<keyof T, NewKey> extends never? Omit<FormBuilder<T, AddProp<T, K, NewKey>>, 'buildCheckbox'> : FormBuilder<T, AddProp<T, K, NewKey>>

Dette sjekker om det finnes noen gjenværende boolske egenskaper, hvis det gjør det returnerer det FormBuilder-typen som før, hvis ikke utelater det 'buildCheckbox'-egenskapen. Imidlertid kan det være tilfeller der jeg vil slå flere funksjoner på eller av. Finnes det en mer konsis måte å legge til/fjerne flere egenskaper hver gang en funksjon kalles?